Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2009-09

getting started with threads

29 messages · 14 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

David Emerson

I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure of the best way to tackle my situation.

I have a program that needs to perform about 10,000 independent tasks, which usually involve waiting for I/O; thus it makes sense to use several threads so that some waiting can be done in parallel. I thought I'd try it with 10 threads.

When a thread completes its task, I'd like it to simply request a new task from the queue. The best way I could think to do this was to have a task assignment function (which accesses a global variable and returns a task id, marking it as pending.)

But how can I prevent race conditions? If threads X and Y happen to call the task assignment function at the same time, it seems to me that they could both be assigned to the same task. I know that this is a problem which has been addressed at great length in the computer science world ... so what's the easiest way for me to address it here and now?

While writing this message it dawned on me that I probably need an event loop (I'm writing a console app) which will, e.g., sleep for 1 second and then check on the status of each thread's work. Then the main loop can assign a new task by starting a new thread, whenever it finds one that is finished. That puts the assignment task back in the main loop where there is no race condition.

Do I even need to send this message? It would be nice to get a little input and see if I'm on the right track :)

Cheers,
David

Reply by email

Mark Emerson
to David Emerson

Nice to see you posted this. Love, Dad

pm David Emerson wrote 36 lines
I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure of the best way to tackle my situation. I have a program that needs to perform about 10,000 independent tasks, which usually involve waiting for I/O; thus it makes sense to use several threads so that some waiting can be done in parallel. I thought I'd try it with 10 threads. When a thread completes its task, I'd like it to simply request a new task from the queue. The best way I could think to do this was to have a task assignment function (which accesses a global variable and returns a task id, marking it as pending.) But how can I prevent race conditions? If threads X and Y happen to call the task assignment function at the same time, it seems to me that they could both be assigned to the same task. I know that this is a problem which has been addressed at great length in the computer science world ... so what's the easiest way for me to address it here and now? While writing this message it dawned on me that I probably need an event loop (I'm writing a console app) which will, e.g., sleep for 1 second and then check on the status of each thread's work. Then the main loop can assign a new task by starting a new thread, whenever it finds one that is finished. That puts the assignment task back in the main loop where there is no race condition. Do I even need to send this message? It would be nice to get a little input and see if I'm on the right track :) Cheers, David _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal

Reply by email

Tobias Giesen
to David Emerson: I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure…
But how can I prevent race conditions? If threads X and Y happen to call the task assignment function at the same time, it seems to me that they could both be assigned to the same task.

Use for example the cross-platform implementations of Enter/LeaveCriticalSection. Like this:

EnterCriticalSection(MyCritVar);
try
  DoStuff
finally
  LeaveCriticalSection(MyCritVar);
end;
Then the main loop can assign a new task by starting a new thread, whenever it finds one > that is finished.

Also possible but not easier. Threads should not be started or stopped for each task, instead they should wait for an event telling them to continue with the next task. For example using SetEvent / ResetEvent as well as:

{$ifdef win32}
WaitForSingleObject(ContinueEvent,INFINITE);
{$else}
RTLEventWaitFor(ContinueEvent);
{$endif}

Cheers,
Tobias

Reply by email

Luca Olivetti
to Tobias Giesen

En/na Tobias Giesen ha escrit:

Show quoted text 10 lines
Also possible but not easier. Threads should not be started or stopped for each task, instead they should wait for an event telling them to continue with the next task. For example using SetEvent / ResetEvent as well as: {$ifdef win32} WaitForSingleObject(ContinueEvent,INFINITE); {$else} RTLEventWaitFor(ContinueEvent); {$endif}

There are some cross-platform synchronization classes in syncobjs

http://www.freepascal.org/docs-html/fcl/syncobjs/index.html

Bye

Luca

Reply by email

Vincent Snijders
to David Emerson: I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure…
David Emerson wrote 7 lines
I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure of the best way to tackle my situation. I have a program that needs to perform about 10,000 independent tasks, which usually involve waiting for I/O; thus it makes sense to use several threads so that some waiting can be done in parallel. I thought I'd try it with 10 threads.

Maybe you can use MTProcs unit: http://wiki.lazarus.freepascal.org/Parallel_procedures

You don't need to worry about assigning tasks anymore.

Vincent

Reply by email

Graeme Geldenhuys
to Luca Olivetti: En/na Tobias Giesen ha escrit: There are some cross-platform synchronization classes in…

Luca Olivetti het geskryf:

There are some cross-platform synchronization classes in syncobjs http://www.freepascal.org/docs-html/fcl/syncobjs/index.html

And I'm working on cross-platform semaphore classes / methods because the current ones in FPC are totally useless.

Regards,
- Graeme -

fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://opensoft.homeip.net/fpgui/

Reply by email

Graeme Geldenhuys
to Jonas Maebe

Jonas Maebe het geskryf:

I guess you mean that there aren't any :)

True and False. :) It was recommended to me that I use the methods directly from the thread manager, then later it was not recommended. Plus the methods available from the thread manager doesn't support initial max values for semaphores.

End result - totally useless. :)

Regards,
- Graeme -

fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://opensoft.homeip.net/fpgui/

Reply by email

Florian Klaempfl
to Graeme Geldenhuys
Show quoted text 9 lines
Jonas Maebe het geskryf: > I guess you mean that there aren't any :) True and False. :) It was recommended to me that I use the methods directly from the thread manager, then later it was not recommended. Plus the methods available from the thread manager doesn't support initial max values for semaphores. End result - totally useless. :)

Hope you find a good cross platform solution :)

Reply by email

Jonas Maebe
to Graeme Geldenhuys: Jonas Maebe het geskryf: True and False. :) It was recommended to me that I use the…
Graeme Geldenhuys wrote 6 lines
Jonas Maebe het geskryf: > > I guess you mean that there aren't any :) True and False. :) It was recommended to me that I use the methods directly from the thread manager, then later it was not recommended.

It's because there is no cross-platform guarantee for any of the thread manager routines (i.c., the semaphore ones are only implemented for Unix platforms).

Jonas

Reply by email

Martin
to Jonas Maebe

just a question:

Should semaphores be on the thread manager?

because imho they are not realy thread related? They can be used by threads, but they have lot's of other uses. Even communication between processes of different applications. They are usually grouped together with other IPC like msg-queues and shared-mem.

my 2 cent

Martin

Jonas Maebe wrote 19 lines
On 18 Sep 2009, at 11:42, Graeme Geldenhuys wrote: > Jonas Maebe het geskryf: >> >> I guess you mean that there aren't any :) > > True and False. :) It was recommended to me that I use the methods > directly from the thread manager, then later it was not recommended. It's because there is no cross-platform guarantee for any of the thread manager routines (i.c., the semaphore ones are only implemented for Unix platforms). Jonas _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal

Reply by email

Jonas Maebe
to Martin
Martin wrote 6 lines
Should semaphores be on the thread manager? because imho they are not realy thread related? They can be used by threads, but they have lot's of other uses. Even communication between processes of different applications. They are usually grouped together with other IPC like msg-queues and shared-mem.

These are unrelated things. As mentioned before, the thread manager is not for public use, so whether or not it has hooks for semaphores is irrelevant w.r.t. what semaphores could theoretically be used for.

Jonas

Reply by email

Graeme Geldenhuys
to Florian Klaempfl: Hope you find a good cross platform solution :)

Florian Klaempfl het geskryf:

Hope you find a good cross platform solution :)

So far I have single interface for create/destroy/lock/unlock of semaphores for Windows, Linux, *BSD. The latter two is actually for all unix with posix systems. So no more IFDEF's and specific uses clauses units in my higher level code.

I only have access to the platforms I mentioned above. Other developers with access to other platforms can implement the remaining implementations, or raise an exception if no implementation exists - to warn the end-user developer.

Regards,
- Graeme -

fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://opensoft.homeip.net/fpgui/

Reply by email

Dariusz Mazur
to David Emerson: I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure…

David Emerson pisze:

Show quoted text 27 lines
I am getting started with threads (linux/cthreads) and I'm very happy so far, but unsure of the best way to tackle my situation. I have a program that needs to perform about 10,000 independent tasks, which usually involve waiting for I/O; thus it makes sense to use several threads so that some waiting can be done in parallel. I thought I'd try it with 10 threads. When a thread completes its task, I'd like it to simply request a new task from the queue. The best way I could think to do this was to have a task assignment function (which accesses a global variable and returns a task id, marking it as pending.) But how can I prevent race conditions? If threads X and Y happen to call the task assignment function at the same time, it seems to me that they could both be assigned to the same task. I know that this is a problem which has been addressed at great length in the computer science world ... so what's the easiest way for me to address it here and now? While writing this message it dawned on me that I probably need an event loop (I'm writing a console app) which will, e.g., sleep for 1 second and then check on the status of each thread's work. Then the main loop can assign a new task by starting a new thread, whenever it finds one that is finished. That puts the assignment task back in the main loop where there is no race condition.

You need FIFO queue for multiple consumer. Each task can be assigned to them.
You can use ordinal tThreadList but it rather slow.

I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to distribute task between threads
its much faster and well scaling on multicore.

Darek

Reply by email

Jonas Maebe
to Dariusz Mazur
Dariusz Mazur wrote
I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to distribute task between threads its much faster and well scaling on multicore.

Note that it won't work as is on non-x86 machines, because it's missing memory barriers (and I think that you may actually need memory barriers on x86 too). Atomic operations are not memory barriers by themselves, and the fact that you perform an atomic operation does not mean that afterwards all cpu's will immediately see this new value.

Jonas

Reply by email

Florian Klaempfl
to Jonas Maebe
Show quoted text 12 lines
On 18 Sep 2009, at 16:24, Dariusz Mazur wrote: > I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to > distribute task between threads > its much faster and well scaling on multicore. Note that it won't work as is on non-x86 machines, because it's missing memory barriers (and I think that you may actually need memory barriers on x86 too). Atomic operations are not memory barriers by themselves, and the fact that you perform an atomic operation does not mean that afterwards all cpu's will immediately see this new value.

On x86 they are.

Reply by email

Dariusz Mazur
to Jonas Maebe: Note that it won't work as is on non-x86 machines, because it's missing memory barriers…

Jonas Maebe pisze:

Show quoted text 12 lines
On 18 Sep 2009, at 16:24, Dariusz Mazur wrote: > I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to > distribute task between threads > its much faster and well scaling on multicore. Note that it won't work as is on non-x86 machines, because it's missing memory barriers (and I think that you may actually need memory barriers on x86 too). Atomic operations are not memory barriers by themselves, and the fact that you perform an atomic operation does not mean that afterwards all cpu's will immediately see this new value.

I don't know other machines. On x86 used atomic operation, as Intel said, all needed cache are resolved.
I've made very stress test on several computers. it works good. Algorithm is very simple, need only 32bit CAS, thus implement it is possible on most platforms.
One thing is needed: multiplatform threadswich.

I use sleep(0) ,but its not best solution (I think).

Darek

Reply by email

Jonas Maebe
to Dariusz Mazur
Dariusz Mazur wrote 16 lines
> > On 18 Sep 2009, at 16:24, Dariusz Mazur wrote: > >> I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to >> distribute task between threads >> its much faster and well scaling on multicore. > > Note that it won't work as is on non-x86 machines, because it's > missing memory barriers (and I think that you may actually need > memory barriers on x86 too). Atomic operations are not memory > barriers by themselves, and the fact that you perform an atomic > operation does not mean that afterwards all cpu's will immediately > see this new value. I don't know other machines. On x86 used atomic operation, as Intel said, all needed cache are resolved. I've made very stress test on several computers. it works good.

At least on PowerPC it will fail (and I guess on SPARC as well).

Show quoted text 5 lines
Algorithm is very simple, need only 32bit CAS, thus implement it is possible on most platforms. One thing is needed: multiplatform threadswich. I use sleep(0) ,but its not best solution (I think).

At least on Mac OS X that will not do anything. There's a procedure called "ThreadSwitch" in the interface of the system unit though. At least for Unix platforms it will work if cthreads is used.

Jonas

Reply by email

Dariusz Mazur
to Jonas Maebe

Jonas Maebe pisze:

Show quoted text 21 lines
On 18 Sep 2009, at 21:30, Dariusz Mazur wrote: >> >> On 18 Sep 2009, at 16:24, Dariusz Mazur wrote: >> >>> I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to >>> distribute task between threads >>> its much faster and well scaling on multicore. >> >> Note that it won't work as is on non-x86 machines, because it's >> missing memory barriers (and I think that you may actually need >> memory barriers on x86 too). Atomic operations are not memory >> barriers by themselves, and the fact that you perform an atomic >> operation does not mean that afterwards all cpu's will immediately >> see this new value. > I don't know other machines. On x86 used atomic operation, as Intel > said, all needed cache are resolved. > I've made very stress test on several computers. it works good. At least on PowerPC it will fail (and I guess on SPARC as well).

I don't say, that this will work on other than x86. I only test on Intel and AMD.

Show quoted text 10 lines
> Algorithm is very simple, need only 32bit CAS, thus implement it is > possible on most platforms. > One thing is needed: multiplatform threadswich. > > I use sleep(0) ,but its not best solution (I think). At least on Mac OS X that will not do anything. There's a procedure called "ThreadSwitch" in the interface of the system unit though. At least for Unix platforms it will work if cthreads is used.

I sow it, but not test.
Seems enough to me.

But is this optimal solution:

procedure SysThreadSwitch;
begin
       Sleep(0);
end;
    WinThreadManager.ThreadSwitch           ;
procedure ThreadSwitch;
begin
  CurrentTM.ThreadSwitch;
end;

We have 2 unnecessary invoke function. Can compiler optimize this? Or maybe better do ThreadSwitch and SysThreadSwitch inlined.

Darek

Reply by email

Jonas Maebe
to Dariusz Mazur
Dariusz Mazur wrote 16 lines
But is this optimal solution: procedure SysThreadSwitch; begin Sleep(0); end; WinThreadManager.ThreadSwitch ; procedure ThreadSwitch; begin CurrentTM.ThreadSwitch; end; We have 2 unnecessary invoke function. Can compiler optimize this? Or maybe better do ThreadSwitch and SysThreadSwitch inlined.

SysThreadSwitch cannot be inlined, because it's called as a procvar. And since you are doing a context switch anyway, the additional overhead of a direct and an indirect call is negligible.

Jonas

Reply by email

Helmut Hartl

-----Original message-----

Earlier message 38 lines
From: Dariusz Mazur Sent: Fri 18-09-2009 16:24 To: FPC-Pascal users discussions ; Subject: Re: [fpc-pascal] getting started with threads > David Emerson pisze: > > I am getting started with threads (linux/cthreads) and I'm very happy so > > far, but unsure of the best way to tackle my situation. > > > > I have a program that needs to perform about 10,000 independent tasks, > > which usually involve waiting for I/O; thus it makes sense to use > > several threads so that some waiting can be done in parallel. I thought > > I'd try it with 10 threads. > You need FIFO queue for multiple consumer. Each task can be assigned to > them. > You can use ordinal tThreadList but it rather slow. > > I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to > distribute task between threads > its much faster and well scaling on multicore. I had (only) a quick look at your implementation but i have the bad feeling that it suffers from the ABA problem. Normally it has a low probability to occur but nevertheless extremely hard to find errors occur occasionally. The problem is avoided by using a 64 Bit CAS with a tagged pointer. I have such a implementation under BSD Licence which i can donate for review and public use. But it only works for 32 Bit x86 archs by now, as i am not sure if there is a consistent support of a 128 Bit CAS on all x86 chips. (my last knowledge was that not all xeon/pentium steppings support it) Have a look at (as example): http://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf Lot's of interesting publications in the field can be found at the ACM (http://www.acm.org/), but you need to be member to have access to the digital library. helmut

Reply by email

Dariusz Mazur
to Helmut Hartl

Helmut Hartl pisze:

Show quoted text 32 lines
-----Original message----- From: Dariusz Mazur Sent: Fri 18-09-2009 16:24 To: FPC-Pascal users discussions ; Subject: Re: [fpc-pascal] getting started with threads > David Emerson pisze: > >> I am getting started with threads (linux/cthreads) and I'm very happy so >> far, but unsure of the best way to tackle my situation. >> >> I have a program that needs to perform about 10,000 independent tasks, >> which usually involve waiting for I/O; thus it makes sense to use >> several threads so that some waiting can be done in parallel. I thought >> I'd try it with 10 threads. >> > You need FIFO queue for multiple consumer. Each task can be assigned to > them. > You can use ordinal tThreadList but it rather slow. > > I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to > distribute task between threads > its much faster and well scaling on multicore. > I had (only) a quick look at your implementation but i have the bad feeling that it suffers from the ABA problem. Normally it has a low probability to occur but nevertheless extremely hard to find errors occur occasionally.

Its save about ABA problem, only it atomic operation are true atomic. I try to test both: theoretical and in real. But if You know sequence, that things may go wrong, pleas tell it.

Show quoted text 6 lines
The problem is avoided by using a 64 Bit CAS with a tagged pointer. I have such a implementation under BSD Licence which i can donate for review and public use. But it only works for 32 Bit x86 archs by now, as i am not sure if there is a consistent support of a 128 Bit CAS on all x86 chips. (my last knowledge was that not all xeon/pentium steppings support it)

64bit CAS is absent on many platforms, and is slower but I'd like too see Your implementation

Show quoted text 5 lines
Have a look at (as example): http://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf Lot's of interesting publications in the field can be found at the ACM (http://www.acm.org/), but you need to be member to have access to the digital library.

thx for links

Darek

Reply by email

Micha Nelissen
to Jonas Maebe: Note that it won't work as is on non-x86 machines, because it's missing memory barriers…
Jonas Maebe wrote
on x86 too). Atomic operations are not memory barriers by themselves, and the fact that you perform an atomic operation does not mean that afterwards all cpu's will immediately see this new value.

Explain? Isn't the point of an atomic update that it doesn't matter whether the other CPU has seen the value yet? Or are we talking about different values?

Micha

Reply by email

Jonas Maebe
to Micha Nelissen
Micha Nelissen wrote 9 lines
Jonas Maebe wrote: > on x86 too). Atomic operations are not memory barriers by > themselves, and the fact that you perform an atomic operation does > not mean that afterwards all cpu's will immediately see this new > value. Explain? Isn't the point of an atomic update that it doesn't matter whether the other CPU has seen the value yet? Or are we talking about different values?

Yes, sorry for being unclear. One problem is that unless all of the data to which the queue elements point is also only loaded/stored using atomic operations, it is possible that the popping cpu will see different values than those intended the pushing cpu (because their caches may not yet have been syncronised). So at least for the pop you need a full memory barrier. There are probably more issues. Here's a page with several different atomic queue implementations for ppc, and all of them contain some form of barrier: http://www.cocoadev.com/index.pl?AtomicSQ

Jonas

Reply by email

Graeme Geldenhuys
to Micha Nelissen: A semaphore is not "locked" and "unlocked", it is posted and waited for. Micha

2009/9/19 Micha Nelissen :

A semaphore is not "locked" and "unlocked", it is posted and waited for.

True, and to let it make even more sense (technically correct) one might have to use method names like deccount() / inccount() or grabitem() / releaseitem() etc etc...

Either way, internal implementation calls the correct post- and wait- API's of each platform.

Regards,
- Graeme -

_______________________________________________
fpGUI - a cross-platform Free Pascal GUI toolkit
http://opensoft.homeip.net/fpgui/

Reply by email

David W Noon
to Micha Nelissen: A semaphore is not "locked" and "unlocked", it is posted and waited for. Micha

On Sat, 19 Sep 2009 10:25:20 +0200, Micha Nelissen wrote about Re: [fpc-pascal] Re: getting started with threads:

Show quoted text 7 lines
Graeme Geldenhuys wrote: > So far I have single interface for create/destroy/lock/unlock of > semaphores for Windows, Linux, *BSD. The latter two is actually for > all A semaphore is not "locked" and "unlocked", it is posted and waited for.

An event semaphore (or "condition" in POSIX-speak) is "waited for" and "posted".

A simple mutex semaphore is "locked" and "unlocked" -- or, more idiomatically, "acquired" and "released".

A read/write mutex semaphore (or "rwlock" in POSIX-speak) is "acquired for sharing" or "acquired for exclusion" and "released".

A Dijkstra semaphore is "upped" or "downed". This is the original usage of the term "semaphore" in computing. It is largely useless in comparison to the other types of semaphore.

Regards, Dave [RLU #314465] ======================================================================= (David W Noon) =======================================================================

Reply by email

Marco van de Voort
to Jonas Maebe: Note that it won't work as is on non-x86 machines, because it's missing memory barriers…

In our previous episode, Jonas Maebe said:

Show quoted text 9 lines
> I use own lockfree FIFO http://www.emadar.com/fpc/lockfree.htm to > distribute task between threads > its much faster and well scaling on multicore. Note that it won't work as is on non-x86 machines, because it's missing memory barriers (and I think that you may actually need memory barriers on x86 too). Atomic operations are not memory barriers by themselves, and the fact that you perform an atomic operation does not mean that afterwards all cpu's will immediately see this new value.

Afaik memory barriers (s/lfence etc) are only needed for I/O mapped memory on x86.

But if you look at the MSDN help for interlocked* functions, you'll seean additional requirement that the arguments are 4-byte aligned. (which might be a requirement of the lock prefix).

Reply by email