Okay. The problem is that I have often seen people construct programs that spawn multiple threads of execution only to go on to try and orchestrate those threads. They end up with hugely complicated sets of locks, states and counters and yields that are a nightmare to balance, and need to be re-balanced after every minor change in the program and, often as not, for each cpu or set of hardware they run on. These type of programs are usually better served using normal event driven/ state machine techniques.

The following program and output shows one way of 'holding' the threads until your ready for them to do some work. The combination of the shared variable $signal, the 'wait' block at the top of the threads, and cond_broadcast( $signal) effect the type of control your looking for.

Code and typical output

#! perl -slw use strict; use threads qw[ yield ]; use threads::shared; $| = 1; our $N ||= 10; our $YIELD ||= 0; my $signal : shared; sub busywork{ my( $counterRef ) = @_; my $self = threads->self->tid(); warn join ' : ', $self, time(), 'waiting'; { ## Wait until the main thread signals the start. my $lock : shared; lock( $lock ); cond_wait( $signal, $lock ); } warn join ' : ', $self, time(), 'starting'; for( 1 .. 1_000_000 ) { $$counterRef++; $YIELD and yield unless $$counterRef % 10000; } warn join ' : ', $self, time(), 'ending'; return 0; } my @counters : shared = ( 0 ) x $N; my @threads; { lock( $signal ); @threads = map{ threads->new( \&busywork, \$counters[ $_ ], \$signal ) } 0..$#counters; sleep 2; ## Give all threads teh chance to reach their starting bl +ocks cond_broadcast( $signal ); ## Go } ## (Crudely) track their progress. ## Remember that the main thread can't run while one of them is. print "@counters" and yield until $_ = grep{ $_ == 1_000_000 } @counters and defined $_ and $_ + = 10; $_->join for @threads; __END__ ## Note: the YIELD parameter was not set for this run. P:\test>355678 1 : 1085288999 : waiting at P:\test\355678.pl line 14. 2 : 1085288999 : waiting at P:\test\355678.pl line 14. 3 : 1085288999 : waiting at P:\test\355678.pl line 14. 4 : 1085288999 : waiting at P:\test\355678.pl line 14. 5 : 1085288999 : waiting at P:\test\355678.pl line 14. 6 : 1085289000 : waiting at P:\test\355678.pl line 14. 7 : 1085289000 : waiting at P:\test\355678.pl line 14. 8 : 1085289000 : waiting at P:\test\355678.pl line 14. 9 : 1085289000 : waiting at P:\test\355678.pl line 14. 10 : 1085289000 : waiting at P:\test\355678.pl line 14. 1 : 1085289002 : starting at P:\test\355678.pl line 21. 2 : 1085289002 : starting at P:\test\355678.pl line 21. 3 : 1085289002 : starting at P:\test\355678.pl line 21. 5 : 1085289002 : starting at P:\test\355678.pl line 21. 29310 140122 27149 0 140702 0 0 0 0 0 29310 176283 27149 0 195750 0 0 0 0 0 84864 359152 27149 0 287224 0 0 0 0 0 10 : 1085289007 : starting at P:\test\355678.pl line 21. 251391 505324 27149 0 360969 0 0 0 0 72076 251391 523617 27149 0 397718 0 0 0 0 72076 343531 578503 27149 0 488759 0 0 0 0 108175 9 : 1085289011 : starting at P:\test\355678.pl line 21. 8 : 1085289011 : starting at P:\test\355678.pl line 21. 7 : 1085289011 : starting at P:\test\355678.pl line 21. 6 : 1085289012 : starting at P:\test\355678.pl line 21. 4 : 1085289014 : starting at P:\test\355678.pl line 21. 454183 651559 81842 72782 506960 53176 311778 180260 89695 162442 454183 651559 191284 236696 506960 301616 421759 180260 89695 162442 454183 651559 263785 437109 506960 319448 421759 180260 89695 162442 454183 651559 300162 453059 506960 319448 421759 180260 89695 162442 454183 651559 300162 453059 542064 319448 421759 180260 89695 162442 454183 688084 300162 453059 596870 319448 421759 180260 89695 162442 454183 834344 300162 453059 651630 319448 421759 180260 89695 162442 454183 852653 300162 453059 706771 319448 421759 180260 89695 162442 7 : 1085289031 : ending at P:\test\355678.pl line 26. 2 : 1085289037 : ending at P:\test\355678.pl line 26. 9 : 1085289041 : ending at P:\test\355678.pl line 26. 1 : 1085289046 : ending at P:\test\355678.pl line 26. 5 : 1085289046 : ending at P:\test\355678.pl line 26. 8 : 1085289048 : ending at P:\test\355678.pl line 26. 6 : 1085289052 : ending at P:\test\355678.pl line 26. 4 : 1085289053 : ending at P:\test\355678.pl line 26. 10 : 1085289053 : ending at P:\test\355678.pl line 26. 3 : 1085289053 : ending at P:\test\355678.pl line 26.

Note that you cannot predict which order the threads will run in; see the apparent randomness of the thread ids as they start/wait/stop and the presence of many 0's in the counters to start.

With the tight thread loop (no yielding), each thread tends to run for extended bursts. Note also the unevenness of the runs, especially at first, and also, that after a couple of timeslices, the main (logging) thread seems to be starved of cpu until the other threads complete.

I believe that the latter effect is a result of dynamic scheduling by the OS. At first it may have 'foreground boost', but when it doesn't make full use of it's first few timeslices, it's priority is dynamically lowered slightly and so takes a back seat until the other threads complete. This is a 'once informed' speculation, but it fits the empirical evidence and some out-of-date knowledge. It is also likely to vary wildly with different OS's and even configurations. Even Workstation and Server configurations of the MS OS's (NT/2000/XP) have different scheduling schemes. I have no knowledge of what, if any, dynamic prioritization is done by *nixes. It is probably configurable through a re-build or maybe even runtime. And the defaults will likely vary from distribution to distribution.

You can achieve a measure of 'even-handedness' between threads by employing yield() and a counter to try and balance out the timeslices between threads. Try running the above snippet with a command line parameter of -YIELD=1. The effect is that you will see that the forground thread gets in to do it's logging with much greater frequency, and that each of the threads appears to be getting it's 'fair share' of the cpu.

Note that it is still non-determanistic in that you will not be able to know or contril which thread gets the next or subsequent time slices, but it does reduce the 'bursty' behaviour somewhat.

However, this has already moved into the realms of 'attempted orchestration' which is fraught with problems and is only as effective as the output you will see seems to imply, because each of the threads is simple and identical. Throw in a thread that starts doing something different, like IO, and you will see dramatically different results in most cases.

Finally, it is usually better to have your threads block on something that denotes the work they have to do, rather than an artificial signal. The Thread::Queue $Q->dequeue() function is a perfect implementation of this.

  1. You create the queue before starting the threads.
  2. Each thread starts and immediately trys to dequeue a piece of work. The queue is empty, so it blocks.
  3. The main (or other) thread then enqueue()s work as it becomes available, and one of the blocking threads unblocks, processes it and the goes back to waiting for some more.

If you have multiple types of work that need to be processed in a particular sequence, use seperate queues for each. The whole chain then becomes data driven rather than managed. It works much better that way.

Runs and output done under Win32XP SP1 and perl 5.8.3 (AS809). The results may be entirely different under other OS's and any conclusions drawn from my observations are only applicable to my view of my system. YMMV.


Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail

In reply to Re: Re: Re: Thread Synchronization Mechanism by BrowserUk
in thread Thread Synchronization Mechanism by pbeckingham

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.