home *** CD-ROM | disk | FTP | other *** search
/ Game Programming - All in One (3rd Edition) / game_prog_all_in_one_3rd_ed.iso / pthread / docs / README.CV < prev    next >
Encoding:
Text File  |  2003-09-12  |  87.1 KB  |  3,037 lines

  1. README.CV -- Condition Variables
  2. --------------------------------
  3.  
  4. The original implementation of condition variables in
  5. pthreads-win32 was based on a discussion paper:
  6.  
  7. "Strategies for Implementing POSIX Condition Variables
  8. on Win32": http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
  9.  
  10. The changes suggested below were made on Feb 6 2001. This
  11. file is included in the package for the benefit of anyone
  12. interested in understanding the pthreads-win32 implementation
  13. of condition variables and the (sometimes subtle) issues that
  14. it attempts to resolve.
  15.  
  16. Thanks go to the individuals whose names appear throughout
  17. the following text.
  18.  
  19. Ross Johnson
  20.  
  21. --------------------------------------------------------------------
  22.  
  23. fyi.. (more detailed problem description/demos + possible fix/patch)
  24.  
  25. regards,
  26. alexander.
  27.  
  28.  
  29. Alexander Terekhov
  30. 31.01.2001 17:43
  31.  
  32. To:   ace-bugs@cs.wustl.edu
  33. cc:
  34. From: Alexander Terekhov/Germany/IBM@IBMDE
  35. Subject:  Implementation of POSIX CVs: spur.wakeups/lost
  36.       signals/deadlocks/unfairness
  37.  
  38.  
  39.  
  40.     ACE VERSION:
  41.  
  42.         5.1.12 (pthread-win32 snapshot 2000-12-29)
  43.  
  44.     HOST MACHINE and OPERATING SYSTEM:
  45.  
  46.         IBM IntelliStation Z Pro, 2 x XEON 1GHz, Win2K
  47.  
  48.     TARGET MACHINE and OPERATING SYSTEM, if different from HOST:
  49.     COMPILER NAME AND VERSION (AND PATCHLEVEL):
  50.  
  51.         Microsoft Visual C++ 6.0
  52.  
  53.     AREA/CLASS/EXAMPLE AFFECTED:
  54.  
  55.         Implementation of POSIX condition variables - OS.cpp/.h
  56.  
  57.     DOES THE PROBLEM AFFECT:
  58.  
  59.         EXECUTION? YES!
  60.  
  61.     SYNOPSIS:
  62.  
  63.         a) spurious wakeups (minor problem)
  64.         b) lost signals
  65.         c) broadcast deadlock
  66.         d) unfairness (minor problem)
  67.  
  68.     DESCRIPTION:
  69.  
  70.         Please see attached copy of discussion thread
  71.         from comp.programming.threads for more details on
  72.         some reported problems. (i've also posted a "fyi"
  73.         message to ace-users a week or two ago but
  74.         unfortunately did not get any response so far).
  75.  
  76.         It seems that current implementation suffers from
  77.         two essential problems:
  78.  
  79.         1) cond.waiters_count does not accurately reflect
  80.            number of waiters blocked on semaphore - w/o
  81.            proper synchronisation that could result (in the
  82.            time window when counter is not accurate)
  83.            in spurious wakeups organised by subsequent
  84.            _signals  and _broadcasts.
  85.  
  86.         2) Always having (with no e.g. copy_and_clear/..)
  87.            the same queue in use (semaphore+counter)
  88.            neither signal nor broadcast provide 'atomic'
  89.            behaviour with respect to other threads/subsequent
  90.            calls to signal/broadcast/wait.
  91.  
  92.         Each problem and combination of both could produce
  93.         various nasty things:
  94.  
  95.         a) spurious wakeups (minor problem)
  96.  
  97.              it is possible that waiter(s) which was already
  98.              unblocked even so is still counted as blocked
  99.              waiter. signal and broadcast will release
  100.              semaphore which will produce a spurious wakeup
  101.              for a 'real' waiter coming later.
  102.  
  103.         b) lost signals
  104.  
  105.              signalling thread ends up consuming its own
  106.              signal. please see demo/discussion below.
  107.  
  108.         c) broadcast deadlock
  109.  
  110.              last_waiter processing code does not correctly
  111.              handle the case with multiple threads
  112.              waiting for the end of broadcast.
  113.              please see demo/discussion below.
  114.  
  115.         d) unfairness (minor problem)
  116.  
  117.              without SignalObjectAndWait some waiter(s)
  118.              may end up consuming broadcasted signals
  119.              multiple times (spurious wakeups) because waiter
  120.              thread(s) can be preempted before they call
  121.              semaphore wait (but after count++ and mtx.unlock).
  122.  
  123.     REPEAT BY:
  124.  
  125.         See below... run problem demos programs (tennis.cpp and
  126.         tennisb.cpp) number of times concurrently (on multiprocessor)
  127.         and in multiple sessions or just add a couple of "Sleep"s
  128.         as described in the attached copy of discussion thread
  129.         from comp.programming.threads
  130.  
  131.     SAMPLE FIX/WORKAROUND:
  132.  
  133.         See attached patch to pthread-win32.. well, I can not
  134.         claim that it is completely bug free but at least my
  135.         test and tests provided by pthreads-win32 seem to work.
  136.         Perhaps that will help.
  137.  
  138.         regards,
  139.         alexander.
  140.  
  141.  
  142. >> Forum: comp.programming.threads
  143. >> Thread: pthread_cond_* implementation questions
  144. .
  145. .
  146. .
  147. David Schwartz <davids@webmaster.com> wrote:
  148.  
  149. > terekhov@my-deja.com wrote:
  150. >
  151. >> BTW, could you please also share your view on other perceived
  152. >> "problems" such as nested broadcast deadlock, spurious wakeups
  153. >> and (the latest one) lost signals??
  154. >
  155. >I'm not sure what you mean. The standard allows an implementation
  156. >to do almost whatever it likes. In fact, you could implement
  157. >pthread_cond_wait by releasing the mutex, sleeping a random
  158. >amount of time, and then reacquiring the mutex. Of course,
  159. >this would be a pretty poor implementation, but any code that
  160. >didn't work under that implementation wouldn't be strictly
  161. >compliant.
  162.  
  163. The implementation you suggested is indeed correct
  164. one (yes, now I see it :). However it requires from
  165. signal/broadcast nothing more than to "{ return 0; }"
  166. That is not the case for pthread-win32 and ACE
  167. implementations. I do think that these implementations
  168. (basically the same implementation) have some serious
  169. problems with wait/signal/broadcast calls. I am looking
  170. for help to clarify whether these problems are real
  171. or not. I think that I can demonstrate what I mean
  172. using one or two small sample programs.
  173. .
  174. .
  175. .
  176. ==========
  177. tennis.cpp
  178. ==========
  179.  
  180. #include "ace/Synch.h"
  181. #include "ace/Thread.h"
  182.  
  183. enum GAME_STATE {
  184.  
  185.   START_GAME,
  186.   PLAYER_A,     // Player A playes the ball
  187.   PLAYER_B,     // Player B playes the ball
  188.   GAME_OVER,
  189.   ONE_PLAYER_GONE,
  190.   BOTH_PLAYERS_GONE
  191.  
  192. };
  193.  
  194. enum GAME_STATE             eGameState;
  195. ACE_Mutex*                  pmtxGameStateLock;
  196. ACE_Condition< ACE_Mutex >* pcndGameStateChange;
  197.  
  198. void*
  199.   playerA(
  200.     void* pParm
  201.   )
  202. {
  203.  
  204.   // For access to game state variable
  205.   pmtxGameStateLock->acquire();
  206.  
  207.   // Play loop
  208.   while ( eGameState < GAME_OVER ) {
  209.  
  210.     // Play the ball
  211.     cout << endl << "PLAYER-A" << endl;
  212.  
  213.     // Now its PLAYER-B's turn
  214.     eGameState = PLAYER_B;
  215.  
  216.     // Signal to PLAYER-B that now it is his turn
  217.     pcndGameStateChange->signal();
  218.  
  219.     // Wait until PLAYER-B finishes playing the ball
  220.     do {
  221.  
  222.       pcndGameStateChange->wait();
  223.  
  224.       if ( PLAYER_B == eGameState )
  225.         cout << endl << "----PLAYER-A: SPURIOUS WAKEUP!!!" << endl;
  226.  
  227.     } while ( PLAYER_B == eGameState );
  228.  
  229.   }
  230.  
  231.   // PLAYER-A gone
  232.   eGameState = (GAME_STATE)(eGameState+1);
  233.   cout << endl << "PLAYER-A GONE" << endl;
  234.  
  235.   // No more access to state variable needed
  236.   pmtxGameStateLock->release();
  237.  
  238.   // Signal PLAYER-A gone event
  239.   pcndGameStateChange->broadcast();
  240.  
  241.   return 0;
  242.  
  243. }
  244.  
  245. void*
  246.   playerB(
  247.     void* pParm
  248.   )
  249. {
  250.  
  251.   // For access to game state variable
  252.   pmtxGameStateLock->acquire();
  253.  
  254.   // Play loop
  255.   while ( eGameState < GAME_OVER ) {
  256.  
  257.     // Play the ball
  258.     cout << endl << "PLAYER-B" << endl;
  259.  
  260.     // Now its PLAYER-A's turn
  261.     eGameState = PLAYER_A;
  262.  
  263.     // Signal to PLAYER-A that now it is his turn
  264.     pcndGameStateChange->signal();
  265.  
  266.     // Wait until PLAYER-A finishes playing the ball
  267.     do {
  268.  
  269.       pcndGameStateChange->wait();
  270.  
  271.       if ( PLAYER_A == eGameState )
  272.         cout << endl << "----PLAYER-B: SPURIOUS WAKEUP!!!" << endl;
  273.  
  274.     } while ( PLAYER_A == eGameState );
  275.  
  276.   }
  277.  
  278.   // PLAYER-B gone
  279.   eGameState = (GAME_STATE)(eGameState+1);
  280.   cout << endl << "PLAYER-B GONE" << endl;
  281.  
  282.   // No more access to state variable needed
  283.   pmtxGameStateLock->release();
  284.  
  285.   // Signal PLAYER-B gone event
  286.   pcndGameStateChange->broadcast();
  287.  
  288.   return 0;
  289.  
  290. }
  291.  
  292.  
  293. int
  294. main (int, ACE_TCHAR *[])
  295. {
  296.  
  297.   pmtxGameStateLock   = new ACE_Mutex();
  298.   pcndGameStateChange = new ACE_Condition< ACE_Mutex >( *pmtxGameStateLock
  299. );
  300.  
  301.   // Set initial state
  302.   eGameState = START_GAME;
  303.  
  304.   // Create players
  305.   ACE_Thread::spawn( playerA );
  306.   ACE_Thread::spawn( playerB );
  307.  
  308.   // Give them 5 sec. to play
  309.   Sleep( 5000 );//sleep( 5 );
  310.  
  311.   // Set game over state
  312.   pmtxGameStateLock->acquire();
  313.   eGameState = GAME_OVER;
  314.  
  315.   // Let them know
  316.   pcndGameStateChange->broadcast();
  317.  
  318.   // Wait for players to stop
  319.   do {
  320.  
  321.     pcndGameStateChange->wait();
  322.  
  323.   } while ( eGameState < BOTH_PLAYERS_GONE );
  324.  
  325.   // Cleanup
  326.   cout << endl << "GAME OVER" << endl;
  327.   pmtxGameStateLock->release();
  328.   delete pcndGameStateChange;
  329.   delete pmtxGameStateLock;
  330.  
  331.   return 0;
  332.  
  333. }
  334.  
  335. ===========
  336. tennisb.cpp
  337. ===========
  338. #include "ace/Synch.h"
  339. #include "ace/Thread.h"
  340.  
  341. enum GAME_STATE {
  342.  
  343.   START_GAME,
  344.   PLAYER_A,     // Player A playes the ball
  345.   PLAYER_B,     // Player B playes the ball
  346.   GAME_OVER,
  347.   ONE_PLAYER_GONE,
  348.   BOTH_PLAYERS_GONE
  349.  
  350. };
  351.  
  352. enum GAME_STATE             eGameState;
  353. ACE_Mutex*                  pmtxGameStateLock;
  354. ACE_Condition< ACE_Mutex >* pcndGameStateChange;
  355.  
  356. void*
  357.   playerA(
  358.     void* pParm
  359.   )
  360. {
  361.  
  362.   // For access to game state variable
  363.   pmtxGameStateLock->acquire();
  364.  
  365.   // Play loop
  366.   while ( eGameState < GAME_OVER ) {
  367.  
  368.     // Play the ball
  369.     cout << endl << "PLAYER-A" << endl;
  370.  
  371.     // Now its PLAYER-B's turn
  372.     eGameState = PLAYER_B;
  373.  
  374.     // Signal to PLAYER-B that now it is his turn
  375.     pcndGameStateChange->broadcast();
  376.  
  377.     // Wait until PLAYER-B finishes playing the ball
  378.     do {
  379.  
  380.       pcndGameStateChange->wait();
  381.  
  382.       if ( PLAYER_B == eGameState )
  383.         cout << endl << "----PLAYER-A: SPURIOUS WAKEUP!!!" << endl;
  384.  
  385.     } while ( PLAYER_B == eGameState );
  386.  
  387.   }
  388.  
  389.   // PLAYER-A gone
  390.   eGameState = (GAME_STATE)(eGameState+1);
  391.   cout << endl << "PLAYER-A GONE" << endl;
  392.  
  393.   // No more access to state variable needed
  394.   pmtxGameStateLock->release();
  395.  
  396.   // Signal PLAYER-A gone event
  397.   pcndGameStateChange->broadcast();
  398.  
  399.   return 0;
  400.  
  401. }
  402.  
  403. void*
  404.   playerB(
  405.     void* pParm
  406.   )
  407. {
  408.  
  409.   // For access to game state variable
  410.   pmtxGameStateLock->acquire();
  411.  
  412.   // Play loop
  413.   while ( eGameState < GAME_OVER ) {
  414.  
  415.     // Play the ball
  416.     cout << endl << "PLAYER-B" << endl;
  417.  
  418.     // Now its PLAYER-A's turn
  419.     eGameState = PLAYER_A;
  420.  
  421.     // Signal to PLAYER-A that now it is his turn
  422.     pcndGameStateChange->broadcast();
  423.  
  424.     // Wait until PLAYER-A finishes playing the ball
  425.     do {
  426.  
  427.       pcndGameStateChange->wait();
  428.  
  429.       if ( PLAYER_A == eGameState )
  430.         cout << endl << "----PLAYER-B: SPURIOUS WAKEUP!!!" << endl;
  431.  
  432.     } while ( PLAYER_A == eGameState );
  433.  
  434.   }
  435.  
  436.   // PLAYER-B gone
  437.   eGameState = (GAME_STATE)(eGameState+1);
  438.   cout << endl << "PLAYER-B GONE" << endl;
  439.  
  440.   // No more access to state variable needed
  441.   pmtxGameStateLock->release();
  442.  
  443.   // Signal PLAYER-B gone event
  444.   pcndGameStateChange->broadcast();
  445.  
  446.   return 0;
  447.  
  448. }
  449.  
  450.  
  451. int
  452. main (int, ACE_TCHAR *[])
  453. {
  454.  
  455.   pmtxGameStateLock   = new ACE_Mutex();
  456.   pcndGameStateChange = new ACE_Condition< ACE_Mutex >( *pmtxGameStateLock
  457. );
  458.  
  459.   // Set initial state
  460.   eGameState = START_GAME;
  461.  
  462.   // Create players
  463.   ACE_Thread::spawn( playerA );
  464.   ACE_Thread::spawn( playerB );
  465.  
  466.   // Give them 5 sec. to play
  467.   Sleep( 5000 );//sleep( 5 );
  468.  
  469.   // Make some noise
  470.   pmtxGameStateLock->acquire();
  471.   cout << endl << "---Noise ON..." << endl;
  472.   pmtxGameStateLock->release();
  473.   for ( int i = 0; i < 100000; i++ )
  474.     pcndGameStateChange->broadcast();
  475.   cout << endl << "---Noise OFF" << endl;
  476.  
  477.   // Set game over state
  478.   pmtxGameStateLock->acquire();
  479.   eGameState = GAME_OVER;
  480.   cout << endl << "---Stopping the game..." << endl;
  481.  
  482.   // Let them know
  483.   pcndGameStateChange->broadcast();
  484.  
  485.   // Wait for players to stop
  486.   do {
  487.  
  488.     pcndGameStateChange->wait();
  489.  
  490.   } while ( eGameState < BOTH_PLAYERS_GONE );
  491.  
  492.   // Cleanup
  493.   cout << endl << "GAME OVER" << endl;
  494.   pmtxGameStateLock->release();
  495.   delete pcndGameStateChange;
  496.   delete pmtxGameStateLock;
  497.  
  498.   return 0;
  499.  
  500. }
  501. .
  502. .
  503. .
  504. David Schwartz <davids@webmaster.com> wrote:
  505. >> > It's compliant
  506. >>
  507. >> That is really good.
  508. >
  509. >> Tomorrow (I have to go urgently now) I will try to
  510. >> demonstrate the lost-signal "problem" of current
  511. >> pthread-win32 and ACE-(variant w/o SingleObjectAndWait)
  512. >> implementations: players start suddenly drop their balls :-)
  513. >> (with no change in source code).
  514. >
  515. >Signals aren't lost, they're going to the main thread,
  516. >which isn't coded correctly to handle them. Try this:
  517. >
  518. >  // Wait for players to stop
  519. >  do {
  520. >
  521. >    pthread_cond_wait( &cndGameStateChange,&mtxGameStateLock );
  522. >printf("Main thread stole a signal\n");
  523. >
  524. >  } while ( eGameState < BOTH_PLAYERS_GONE );
  525. >
  526. >I bet everytime you thing a signal is lost, you'll see that printf.
  527. >The signal isn't lost, it was stolen by another thread.
  528.  
  529. well, you can probably loose your bet.. it was indeed stolen
  530. by "another" thread but not the one you seem to think of.
  531.  
  532. I think that what actually happens is the following:
  533.  
  534. H:\SA\UXX\pt\PTHREADS\TESTS>tennis3.exe
  535.  
  536. PLAYER-A
  537.  
  538. PLAYER-B
  539.  
  540. ----PLAYER-B: SPURIOUS WAKEUP!!!
  541.  
  542. PLAYER-A GONE
  543.  
  544. PLAYER-B GONE
  545.  
  546. GAME OVER
  547.  
  548. H:\SA\UXX\pt\PTHREADS\TESTS>
  549.  
  550. here you can see that PLAYER-B after playing his first
  551. ball (which came via signal from PLAYER-A) just dropped
  552. it down. What happened is that his signal to player A
  553. was consumed as spurious wakeup by himself (player B).
  554.  
  555. The implementation has a problem:
  556.  
  557. ================
  558. waiting threads:
  559. ================
  560.  
  561. { /** Critical Section
  562.  
  563.   inc cond.waiters_count
  564.  
  565. }
  566.  
  567.   /*
  568.   /* Atomic only if using Win32 SignalObjectAndWait
  569.   /*
  570.   cond.mtx.release
  571.  
  572.   /*** ^^-- A THREAD WHICH DID SIGNAL MAY ACQUIRE THE MUTEX,
  573.   /***      GO INTO WAIT ON THE SAME CONDITION AND OVERTAKE
  574.   /***      ORIGINAL WAITER(S) CONSUMING ITS OWN SIGNAL!
  575.  
  576.   cond.sem.wait
  577.  
  578. Player-A after playing game's initial ball went into
  579. wait (called _wait) but was pre-empted before reaching
  580. wait semaphore. He was counted as waiter but was not
  581. actually waiting/blocked yet.
  582.  
  583. ===============
  584. signal threads:
  585. ===============
  586.  
  587. { /** Critical Section
  588.  
  589.   waiters_count = cond.waiters_count
  590.  
  591. }
  592.  
  593.   if ( waiters_count != 0 )
  594.  
  595.     sem.post 1
  596.  
  597.   endif
  598.  
  599. Player-B after he received signal/ball from Player A
  600. called _signal. The _signal did see that there was
  601. one waiter blocked on the condition (Player-A) and
  602. released the semaphore.. (but it did not unblock
  603. Player-A because he was not actually blocked).
  604. Player-B thread continued its execution, called _wait,
  605. was counted as second waiter BUT was allowed to slip
  606. through opened semaphore gate (which was opened for
  607. Player-B) and received his own signal. Player B remained
  608. blocked followed by Player A. Deadlock happened which
  609. lasted until main thread came in and said game over.
  610.  
  611. It seems to me that the implementation fails to
  612. correctly implement the following statement
  613. from specification:
  614.  
  615. http://www.opengroup.org/
  616. onlinepubs/007908799/xsh/pthread_cond_wait.html
  617.  
  618. "These functions atomically release mutex and cause
  619. the calling thread to block on the condition variable
  620. cond; atomically here means "atomically with respect
  621. to access by another thread to the mutex and then the
  622. condition variable". That is, if another thread is
  623. able to acquire the mutex after the about-to-block
  624. thread has released it, then a subsequent call to
  625. pthread_cond_signal() or pthread_cond_broadcast()
  626. in that thread behaves as if it were issued after
  627. the about-to-block thread has blocked."
  628.  
  629. Question: Am I right?
  630.  
  631. (I produced the program output above by simply
  632. adding ?Sleep( 1 )?:
  633.  
  634. ================
  635. waiting threads:
  636. ================
  637.  
  638. { /** Critical Section
  639.  
  640.   inc cond.waiters_count
  641.  
  642. }
  643.  
  644.   /*
  645.   /* Atomic only if using Win32 SignalObjectAndWait
  646.   /*
  647.   cond.mtx.release
  648.  
  649. Sleep( 1 ); // Win32
  650.  
  651.   /*** ^^-- A THREAD WHICH DID SIGNAL MAY ACQUIRE THE MUTEX,
  652.   /***      GO INTO WAIT ON THE SAME CONDITION AND OVERTAKE
  653.   /***      ORIGINAL WAITER(S) CONSUMING ITS OWN SIGNAL!
  654.  
  655.   cond.sem.wait
  656.  
  657. to the source code of pthread-win32 implementation:
  658.  
  659. http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/
  660. condvar.c?rev=1.36&content-type=text/
  661. x-cvsweb-markup&cvsroot=pthreads-win32
  662.  
  663.  
  664.   /*
  665.   * We keep the lock held just long enough to increment the count of
  666.   * waiters by one (above).
  667.   * Note that we can't keep it held across the
  668.   * call to sem_wait since that will deadlock other calls
  669.   * to pthread_cond_signal
  670.   */
  671.   cleanup_args.mutexPtr = mutex;
  672.   cleanup_args.cv = cv;
  673.   cleanup_args.resultPtr = &result;
  674.  
  675.   pthread_cleanup_push (ptw32_cond_wait_cleanup, (void *)
  676. &cleanup_args);
  677.  
  678.   if ((result = pthread_mutex_unlock (mutex)) == 0)
  679.     {((result
  680. Sleep( 1 ); // @AT
  681.  
  682.       /*
  683.       * Wait to be awakened by
  684.       *              pthread_cond_signal, or
  685.       *              pthread_cond_broadcast, or
  686.       *              a timeout
  687.       *
  688.       * Note:
  689.       *      ptw32_sem_timedwait is a cancelation point,
  690.       *      hence providing the
  691.       *      mechanism for making pthread_cond_wait a cancelation
  692.       *      point. We use the cleanup mechanism to ensure we
  693.       *      re-lock the mutex and decrement the waiters count
  694.       *      if we are canceled.
  695.       */
  696.       if (ptw32_sem_timedwait (&(cv->sema), abstime) == -1)         {
  697.           result = errno;
  698.         }
  699.     }
  700.  
  701.   pthread_cleanup_pop (1);  /* Always cleanup */
  702.  
  703.  
  704. BTW, on my system (2 CPUs) I can manage to get
  705. signals lost even without any source code modification
  706. if I run the tennis program many times in different
  707. shell sessions.
  708. .
  709. .
  710. .
  711. David Schwartz <davids@webmaster.com> wrote:
  712. >terekhov@my-deja.com wrote:
  713. >
  714. >> well, it might be that the program is in fact buggy.
  715. >> but you did not show me any bug.
  716. >
  717. >You're right. I was close but not dead on. I was correct, however,
  718. >that the code is buggy because it uses 'pthread_cond_signal' even
  719. >though not any thread waiting on the condition variable can do the
  720. >job. I was wrong in which thread could be waiting on the cv but
  721. >unable to do the job.
  722.  
  723. Okay, lets change 'pthread_cond_signal' to 'pthread_cond_broadcast'
  724. but also add some noise from main() right before declaring the game
  725. to be over (I need it in order to demonstrate another problem of
  726. pthread-win32/ACE implementations - broadcast deadlock)...
  727. .
  728. .
  729. .
  730. It is my understanding of POSIX conditions,
  731. that on correct implementation added noise
  732. in form of unnecessary broadcasts from main,
  733. should not break the tennis program. The
  734. only 'side effect' of added noise on correct
  735. implementation would be 'spurious wakeups' of
  736. players (in fact they are not spurious,
  737. players just see them as spurious) unblocked,
  738. not by another player but by main before
  739. another player had a chance to acquire the
  740. mutex and change the game state variable:
  741. .
  742. .
  743. .
  744.  
  745. PLAYER-B
  746.  
  747. PLAYER-A
  748.  
  749. ---Noise ON...
  750.  
  751. PLAYER-B
  752.  
  753. PLAYER-A
  754.  
  755. .
  756. .
  757. .
  758.  
  759. PLAYER-B
  760.  
  761. PLAYER-A
  762.  
  763. ----PLAYER-A: SPURIOUS WAKEUP!!!
  764.  
  765. PLAYER-B
  766.  
  767. PLAYER-A
  768.  
  769. ---Noise OFF
  770.  
  771. PLAYER-B
  772.  
  773. ---Stopping the game...
  774.  
  775. PLAYER-A GONE
  776.  
  777. PLAYER-B GONE
  778.  
  779. GAME OVER
  780.  
  781. H:\SA\UXX\pt\PTHREADS\TESTS>
  782.  
  783. On pthread-win32/ACE implementations the
  784. program could stall:
  785.  
  786. .
  787. .
  788. .
  789.  
  790. PLAYER-A
  791.  
  792. PLAYER-B
  793.  
  794. PLAYER-A
  795.  
  796. PLAYER-B
  797.  
  798. PLAYER-A
  799.  
  800. PLAYER-B
  801.  
  802. PLAYER-A
  803.  
  804. PLAYER-B
  805.  
  806. ---Noise ON...
  807.  
  808. PLAYER-A
  809.  
  810. ---Noise OFF
  811. ^C
  812. H:\SA\UXX\pt\PTHREADS\TESTS>
  813.  
  814.  
  815. The implementation has problems:
  816.  
  817. ================
  818. waiting threads:
  819. ================
  820.  
  821. { /** Critical Section
  822.  
  823.   inc cond.waiters_count
  824.  
  825. }
  826.  
  827.   /*
  828.   /* Atomic only if using Win32 SignalObjectAndWait
  829.   /*
  830.   cond.mtx.release
  831.   cond.sem.wait
  832.  
  833.   /*** ^^-- WAITER CAN BE PREEMPTED AFTER BEING UNBLOCKED...
  834.  
  835. { /** Critical Section
  836.  
  837.   dec cond.waiters_count
  838.  
  839.   /*** ^^- ...AND BEFORE DECREMENTING THE COUNT (1)
  840.  
  841.   last_waiter = ( cond.was_broadcast &&
  842.                     cond.waiters_count == 0 )
  843.  
  844.   if ( last_waiter )
  845.  
  846.     cond.was_broadcast = FALSE
  847.  
  848.   endif
  849.  
  850. }
  851.  
  852.   if ( last_waiter )
  853.  
  854.     /*
  855.     /* Atomic only if using Win32 SignalObjectAndWait
  856.     /*
  857.     cond.auto_reset_event_or_sem.post /* Event for Win32
  858.     cond.mtx.acquire
  859.  
  860.   /*** ^^-- ...AND BEFORE CALL TO mtx.acquire (2)
  861.  
  862.   /*** ^^-- NESTED BROADCASTS RESULT IN A DEADLOCK
  863.  
  864.  
  865.   else
  866.  
  867.     cond.mtx.acquire
  868.  
  869.   /*** ^^-- ...AND BEFORE CALL TO mtx.acquire (3)
  870.  
  871.   endif
  872.  
  873.  
  874. ==================
  875. broadcast threads:
  876. ==================
  877.  
  878. { /** Critical Section
  879.  
  880.   waiters_count = cond.waiters_count
  881.  
  882.   if ( waiters_count != 0 )
  883.  
  884.     cond.was_broadcast = TRUE
  885.  
  886.   endif
  887.  
  888. }
  889.  
  890. if ( waiters_count != 0 )
  891.  
  892.   cond.sem.post waiters_count
  893.  
  894.   /*** ^^^^^--- SPURIOUS WAKEUPS DUE TO (1)
  895.  
  896.   cond.auto_reset_event_or_sem.wait /* Event for Win32
  897.  
  898.   /*** ^^^^^--- DEADLOCK FOR FURTHER BROADCASTS IF THEY
  899.                 HAPPEN TO GO INTO WAIT WHILE PREVIOUS
  900.                 BROADCAST IS STILL IN PROGRESS/WAITING
  901.  
  902. endif
  903.  
  904. a) cond.waiters_count does not accurately reflect
  905. number of waiters blocked on semaphore - that could
  906. result (in the time window when counter is not accurate)
  907. in spurios wakeups organised by subsequent _signals
  908. and _broadcasts. From standard compliance point of view
  909. that is OK but that could be a real problem from
  910. performance/efficiency point of view.
  911.  
  912. b) If subsequent broadcast happen to go into wait on
  913. cond.auto_reset_event_or_sem before previous
  914. broadcast was unblocked from cond.auto_reset_event_or_sem
  915. by its last waiter, one of two blocked threads will
  916. remain blocked because last_waiter processing code
  917. fails to unblock both threads.
  918.  
  919. In the situation with tennisb.c the Player-B was put
  920. in a deadlock by noise (broadcast) coming from main
  921. thread. And since Player-B holds the game state
  922. mutex when it calls broadcast, the whole program
  923. stalled: Player-A was deadlocked on mutex and
  924. main thread after finishing with producing the noise
  925. was deadlocked on mutex too (needed to declare the
  926. game over)
  927.  
  928. (I produced the program output above by simply
  929. adding ?Sleep( 1 )?:
  930.  
  931. ==================
  932. broadcast threads:
  933. ==================
  934.  
  935. { /** Critical Section
  936.  
  937.   waiters_count = cond.waiters_count
  938.  
  939.   if ( waiters_count != 0 )
  940.  
  941.     cond.was_broadcast = TRUE
  942.  
  943.   endif
  944.  
  945. }
  946.  
  947. if ( waiters_count != 0 )
  948.  
  949. Sleep( 1 ); //Win32
  950.  
  951.   cond.sem.post waiters_count
  952.  
  953.   /*** ^^^^^--- SPURIOUS WAKEUPS DUE TO (1)
  954.  
  955.   cond.auto_reset_event_or_sem.wait /* Event for Win32
  956.  
  957.   /*** ^^^^^--- DEADLOCK FOR FURTHER BROADCASTS IF THEY
  958.                 HAPPEN TO GO INTO WAIT WHILE PREVIOUS
  959.                 BROADCAST IS STILL IN PROGRESS/WAITING
  960.  
  961. endif
  962.  
  963. to the source code of pthread-win32 implementation:
  964.  
  965. http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/
  966. condvar.c?rev=1.36&content-type=text/
  967. x-cvsweb-markup&cvsroot=pthreads-win32
  968.  
  969.   if (wereWaiters)
  970.     {(wereWaiters)sroot=pthreads-win32eb.cgi/pthreads/Yem...m
  971.       /*
  972.       * Wake up all waiters
  973.       */
  974.  
  975. Sleep( 1 ); //@AT
  976.  
  977. #ifdef NEED_SEM
  978.  
  979.       result = (ptw32_increase_semaphore( &cv->sema, cv->waiters )
  980.                  ? 0
  981.                 : EINVAL);
  982.  
  983. #else /* NEED_SEM */
  984.  
  985.       result = (ReleaseSemaphore( cv->sema, cv->waiters, NULL )
  986.                  ? 0
  987.                 : EINVAL);
  988.  
  989. #endif /* NEED_SEM */
  990.  
  991.     }
  992.  
  993.   (void) pthread_mutex_unlock(&(cv->waitersLock));
  994.  
  995.   if (wereWaiters && result == 0)
  996.     {(wereWaiters
  997.       /*
  998.        * Wait for all the awakened threads to acquire their part of
  999.        * the counting semaphore
  1000.        */
  1001.  
  1002.       if (WaitForSingleObject (cv->waitersDone, INFINITE)
  1003.           == WAIT_OBJECT_0)
  1004.         {
  1005.           result = 0;
  1006.         }
  1007.       else
  1008.         {
  1009.           result = EINVAL;
  1010.         }
  1011.  
  1012.     }
  1013.  
  1014.   return (result);
  1015.  
  1016. }
  1017.  
  1018. BTW, on my system (2 CPUs) I can manage to get
  1019. the program stalled even without any source code
  1020. modification if I run the tennisb program many
  1021. times in different shell sessions.
  1022.  
  1023. ===================
  1024. pthread-win32 patch
  1025. ===================
  1026. struct pthread_cond_t_ {
  1027.   long            nWaitersBlocked;   /* Number of threads blocked
  1028. */
  1029.   long            nWaitersUnblocked; /* Number of threads unblocked
  1030. */
  1031.   long            nWaitersToUnblock; /* Number of threads to unblock
  1032. */
  1033.   sem_t           semBlockQueue;     /* Queue up threads waiting for the
  1034. */
  1035.                                      /*   condition to become signalled
  1036. */
  1037.   sem_t           semBlockLock;      /* Semaphore that guards access to
  1038. */
  1039.                                      /* | waiters blocked count/block queue
  1040. */
  1041.                                      /* +-> Mandatory Sync.LEVEL-1
  1042. */
  1043.   pthread_mutex_t mtxUnblockLock;    /* Mutex that guards access to
  1044. */
  1045.                                      /* | waiters (to)unblock(ed) counts
  1046. */
  1047.                                      /* +-> Optional* Sync.LEVEL-2
  1048. */
  1049. };                                   /* Opt*) for _timedwait and
  1050. cancellation*/
  1051.  
  1052. int
  1053. pthread_cond_init (pthread_cond_t * cond, const pthread_condattr_t * attr)
  1054.   int result = EAGAIN;
  1055.   pthread_cond_t cv = NULL;
  1056.  
  1057.   if (cond == NULL)
  1058.     {(cond
  1059.       return EINVAL;
  1060.     }
  1061.  
  1062.   if ((attr != NULL && *attr != NULL) &&
  1063.       ((*attr)->pshared == PTHREAD_PROCESS_SHARED))
  1064.     {
  1065.       /*
  1066.        * Creating condition variable that can be shared between
  1067.        * processes.
  1068.        */
  1069.       result = ENOSYS;
  1070.  
  1071.       goto FAIL0;
  1072.     }
  1073.  
  1074.   cv = (pthread_cond_t) calloc (1, sizeof (*cv));
  1075.  
  1076.   if (cv == NULL)
  1077.     {(cv
  1078.       result = ENOMEM;
  1079.       goto FAIL0;
  1080.     }
  1081.  
  1082.   cv->nWaitersBlocked   = 0;
  1083.   cv->nWaitersUnblocked = 0;
  1084.   cv->nWaitersToUnblock = 0;
  1085.  
  1086.   if (sem_init (&(cv->semBlockLock), 0, 1) != 0)
  1087.     {(sem_init
  1088.       goto FAIL0;
  1089.     }
  1090.  
  1091.   if (sem_init (&(cv->semBlockQueue), 0, 0) != 0)
  1092.     {(sem_init
  1093.       goto FAIL1;
  1094.     }
  1095.  
  1096.   if (pthread_mutex_init (&(cv->mtxUnblockLock), 0) != 0)
  1097.     {(pthread_mutex_init
  1098.       goto FAIL2;
  1099.     }
  1100.  
  1101.  
  1102.   result = 0;
  1103.  
  1104.   goto DONE;
  1105.  
  1106.   /*
  1107.    * -------------
  1108.    * Failed...
  1109.    * -------------
  1110.    */
  1111. FAIL2:
  1112.   (void) sem_destroy (&(cv->semBlockQueue));
  1113.  
  1114. FAIL1:
  1115.   (void) sem_destroy (&(cv->semBlockLock));
  1116.  
  1117. FAIL0:
  1118. DONE:
  1119.   *cond = cv;
  1120.  
  1121.   return (result);
  1122.  
  1123. }                               /* pthread_cond_init */
  1124.  
  1125. int
  1126. pthread_cond_destroy (pthread_cond_t * cond)
  1127. {
  1128.   int result = 0;
  1129.   pthread_cond_t cv;
  1130.  
  1131.   /*
  1132.    * Assuming any race condition here is harmless.
  1133.    */
  1134.   if (cond == NULL
  1135.       || *cond == NULL)
  1136.     {
  1137.       return EINVAL;
  1138.     }
  1139.  
  1140.   if (*cond != (pthread_cond_t) PTW32_OBJECT_AUTO_INIT)
  1141.     {(*cond
  1142.       cv = *cond;
  1143.  
  1144.       /*
  1145.        * Synchronize access to waiters blocked count (LEVEL-1)
  1146.        */
  1147.       if (sem_wait(&(cv->semBlockLock)) != 0)
  1148.         {(sem_wait(&(cv->semBlockLock))
  1149.           return errno;
  1150.         }
  1151.  
  1152.       /*
  1153.        * Synchronize access to waiters (to)unblock(ed) counts (LEVEL-2)
  1154.        */
  1155.       if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0)
  1156.         {((result
  1157.           (void) sem_post(&(cv->semBlockLock));
  1158.           return result;
  1159.         }
  1160.  
  1161.       /*
  1162.        * Check whether cv is still busy (still has waiters blocked)
  1163.        */
  1164.       if (cv->nWaitersBlocked - cv->nWaitersUnblocked > 0)
  1165.         {(cv->nWaitersBlocked
  1166.           (void) sem_post(&(cv->semBlockLock));
  1167.           (void) pthread_mutex_unlock(&(cv->mtxUnblockLock));
  1168.           return EBUSY;
  1169.         }
  1170.  
  1171.       /*
  1172.        * Now it is safe to destroy
  1173.        */
  1174.       (void) sem_destroy (&(cv->semBlockLock));
  1175.       (void) sem_destroy (&(cv->semBlockQueue));
  1176.       (void) pthread_mutex_unlock (&(cv->mtxUnblockLock));
  1177.       (void) pthread_mutex_destroy (&(cv->mtxUnblockLock));
  1178.  
  1179.       free(cv);
  1180.       *cond = NULL;
  1181.     }
  1182.   else
  1183.     {
  1184.       /*
  1185.        * See notes in ptw32_cond_check_need_init() above also.
  1186.        */
  1187.       EnterCriticalSection(&ptw32_cond_test_init_lock);
  1188.  
  1189.       /*
  1190.        * Check again.
  1191.        */
  1192.       if (*cond == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT)
  1193.         {(*cond
  1194.           /*
  1195.            * This is all we need to do to destroy a statically
  1196.            * initialised cond that has not yet been used (initialised).
  1197.            * If we get to here, another thread
  1198.            * waiting to initialise this cond will get an EINVAL.
  1199.            */
  1200.           *cond = NULL;
  1201.         }
  1202.       else
  1203.         {
  1204.           /*
  1205.            * The cv has been initialised while we were waiting
  1206.            * so assume it's in use.
  1207.            */
  1208.           result = EBUSY;
  1209.         }
  1210.  
  1211.       LeaveCriticalSection(&ptw32_cond_test_init_lock);
  1212.     }
  1213.  
  1214.   return (result);
  1215. }
  1216.  
  1217. /*
  1218.  * Arguments for cond_wait_cleanup, since we can only pass a
  1219.  * single void * to it.
  1220.  */
  1221. typedef struct {
  1222.   pthread_mutex_t * mutexPtr;
  1223.   pthread_cond_t cv;
  1224.   int * resultPtr;
  1225. } ptw32_cond_wait_cleanup_args_t;
  1226.  
  1227. static void
  1228. ptw32_cond_wait_cleanup(void * args)
  1229. {
  1230.   ptw32_cond_wait_cleanup_args_t * cleanup_args =
  1231. (ptw32_cond_wait_cleanup_args_t *) args;
  1232.   pthread_cond_t cv = cleanup_args->cv;
  1233.   int * resultPtr = cleanup_args->resultPtr;
  1234.   int eLastSignal; /* enum: 1=yes 0=no -1=cancelled/timedout w/o signal(s)
  1235. */
  1236.   int result;
  1237.  
  1238.   /*
  1239.    * Whether we got here as a result of signal/broadcast or because of
  1240.    * timeout on wait or thread cancellation we indicate that we are no
  1241.    * longer waiting. The waiter is responsible for adjusting waiters
  1242.    * (to)unblock(ed) counts (protected by unblock lock).
  1243.    * Unblock lock/Sync.LEVEL-2 supports _timedwait and cancellation.
  1244.    */
  1245.   if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0)
  1246.     {((result
  1247.       *resultPtr = result;
  1248.       return;
  1249.     }
  1250.  
  1251.   cv->nWaitersUnblocked++;
  1252.  
  1253.   eLastSignal = (cv->nWaitersToUnblock == 0) ?
  1254.                    -1 : (--cv->nWaitersToUnblock == 0);
  1255.  
  1256.   /*
  1257.    * No more LEVEL-2 access to waiters (to)unblock(ed) counts needed
  1258.    */
  1259.   if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0)
  1260.     {((result
  1261.       *resultPtr = result;
  1262.       return;
  1263.     }
  1264.  
  1265.   /*
  1266.    * If last signal...
  1267.    */
  1268.   if (eLastSignal == 1)
  1269.     {(eLastSignal
  1270.      /*
  1271.       * ...it means that we have end of 'atomic' signal/broadcast
  1272.       */
  1273.       if (sem_post(&(cv->semBlockLock)) != 0)
  1274.         {(sem_post(&(cv->semBlockLock))
  1275.           *resultPtr = errno;
  1276.           return;
  1277.         }
  1278.     }
  1279.   /*
  1280.    * If not last signal and not timed out/cancelled wait w/o signal...
  1281.    */
  1282.   else if (eLastSignal == 0)
  1283.     {
  1284.      /*
  1285.       * ...it means that next waiter can go through semaphore
  1286.       */
  1287.       if (sem_post(&(cv->semBlockQueue)) != 0)
  1288.         {(sem_post(&(cv->semBlockQueue))
  1289.           *resultPtr = errno;
  1290.           return;
  1291.         }
  1292.     }
  1293.  
  1294.   /*
  1295.    * XSH: Upon successful return, the mutex has been locked and is owned
  1296.    * by the calling thread
  1297.    */
  1298.   if ((result = pthread_mutex_lock(cleanup_args->mutexPtr)) != 0)
  1299.     {((result
  1300.       *resultPtr = result;
  1301.     }
  1302.  
  1303. }                               /* ptw32_cond_wait_cleanup */
  1304.  
  1305. static int
  1306. ptw32_cond_timedwait (pthread_cond_t * cond,
  1307.                       pthread_mutex_t * mutex,
  1308.                       const struct timespec *abstime)
  1309. {
  1310.   int result = 0;
  1311.   pthread_cond_t cv;
  1312.   ptw32_cond_wait_cleanup_args_t cleanup_args;
  1313.  
  1314.   if (cond == NULL || *cond == NULL)
  1315.     {(cond
  1316.       return EINVAL;
  1317.     }
  1318.  
  1319.   /*
  1320.    * We do a quick check to see if we need to do more work
  1321.    * to initialise a static condition variable. We check
  1322.    * again inside the guarded section of ptw32_cond_check_need_init()
  1323.    * to avoid race conditions.
  1324.    */
  1325.   if (*cond == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT)
  1326.     {(*cond
  1327.       result = ptw32_cond_check_need_init(cond);
  1328.     }
  1329.  
  1330.   if (result != 0 && result != EBUSY)
  1331.     {(result
  1332.       return result;
  1333.     }
  1334.  
  1335.   cv = *cond;
  1336.  
  1337.   /*
  1338.    * Synchronize access to waiters blocked count (LEVEL-1)
  1339.    */
  1340.   if (sem_wait(&(cv->semBlockLock)) != 0)
  1341.     {(sem_wait(&(cv->semBlockLock))
  1342.       return errno;
  1343.     }
  1344.  
  1345.   cv->nWaitersBlocked++;
  1346.  
  1347.   /*
  1348.    * Thats it. Counted means waiting, no more access needed
  1349.    */
  1350.   if (sem_post(&(cv->semBlockLock)) != 0)
  1351.     {(sem_post(&(cv->semBlockLock))
  1352.       return errno;
  1353.     }
  1354.  
  1355.   /*
  1356.    * Setup this waiter cleanup handler
  1357.    */
  1358.   cleanup_args.mutexPtr = mutex;
  1359.   cleanup_args.cv = cv;
  1360.   cleanup_args.resultPtr = &result;
  1361.  
  1362.   pthread_cleanup_push (ptw32_cond_wait_cleanup, (void *) &cleanup_args);
  1363.  
  1364.   /*
  1365.    * Now we can release 'mutex' and...
  1366.    */
  1367.   if ((result = pthread_mutex_unlock (mutex)) == 0)
  1368.     {((result
  1369.  
  1370.       /*
  1371.        * ...wait to be awakened by
  1372.        *              pthread_cond_signal, or
  1373.        *              pthread_cond_broadcast, or
  1374.        *              timeout, or
  1375.        *              thread cancellation
  1376.        *
  1377.        * Note:
  1378.        *
  1379.        *      ptw32_sem_timedwait is a cancellation point,
  1380.        *      hence providing the mechanism for making
  1381.        *      pthread_cond_wait a cancellation point.
  1382.        *      We use the cleanup mechanism to ensure we
  1383.        *      re-lock the mutex and adjust (to)unblock(ed) waiters
  1384.        *      counts if we are cancelled, timed out or signalled.
  1385.        */
  1386.       if (ptw32_sem_timedwait (&(cv->semBlockQueue), abstime) != 0)
  1387.         {(ptw32_sem_timedwait
  1388.           result = errno;
  1389.         }
  1390.     }
  1391.  
  1392.   /*
  1393.    * Always cleanup
  1394.    */
  1395.   pthread_cleanup_pop (1);
  1396.  
  1397.  
  1398.   /*
  1399.    * "result" can be modified by the cleanup handler.
  1400.    */
  1401.   return (result);
  1402.  
  1403. }                               /* ptw32_cond_timedwait */
  1404.  
  1405.  
  1406. static int
  1407. ptw32_cond_unblock (pthread_cond_t * cond,
  1408.                     int unblockAll)
  1409. {
  1410.   int result;
  1411.   pthread_cond_t cv;
  1412.  
  1413.   if (cond == NULL || *cond == NULL)
  1414.     {(cond
  1415.       return EINVAL;
  1416.     }
  1417.  
  1418.   cv = *cond;
  1419.  
  1420.   /*
  1421.    * No-op if the CV is static and hasn't been initialised yet.
  1422.    * Assuming that any race condition is harmless.
  1423.    */
  1424.   if (cv == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT)
  1425.     {(cv
  1426.       return 0;
  1427.     }
  1428.  
  1429.   /*
  1430.    * Synchronize access to waiters blocked count (LEVEL-1)
  1431.    */
  1432.   if (sem_wait(&(cv->semBlockLock)) != 0)
  1433.     {(sem_wait(&(cv->semBlockLock))
  1434.       return errno;
  1435.     }
  1436.  
  1437.   /*
  1438.    * Synchronize access to waiters (to)unblock(ed) counts (LEVEL-2)
  1439.    * This sync.level supports _timedwait and cancellation
  1440.    */
  1441.   if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0)
  1442.     {((result
  1443.       return result;
  1444.     }
  1445.  
  1446.   /*
  1447.    * Adjust waiters blocked and unblocked counts (collect garbage)
  1448.    */
  1449.   if (cv->nWaitersUnblocked != 0)
  1450.     {(cv->nWaitersUnblocked
  1451.       cv->nWaitersBlocked  -= cv->nWaitersUnblocked;
  1452.       cv->nWaitersUnblocked = 0;
  1453.     }
  1454.  
  1455.   /*
  1456.    * If (after adjustment) there are still some waiters blocked counted...
  1457.    */
  1458.   if ( cv->nWaitersBlocked > 0)
  1459.     {(
  1460.       /*
  1461.        * We will unblock first waiter and leave semBlockLock/LEVEL-1 locked
  1462.        * LEVEL-1 access is left disabled until last signal/unblock
  1463. completes
  1464.        */
  1465.       cv->nWaitersToUnblock = (unblockAll) ? cv->nWaitersBlocked : 1;
  1466.  
  1467.       /*
  1468.        * No more LEVEL-2 access to waiters (to)unblock(ed) counts needed
  1469.        * This sync.level supports _timedwait and cancellation
  1470.        */
  1471.       if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0)
  1472.         {((result
  1473.           return result;
  1474.         }
  1475.  
  1476.  
  1477.       /*
  1478.        * Now, with LEVEL-2 lock released let first waiter go through
  1479. semaphore
  1480.        */
  1481.       if (sem_post(&(cv->semBlockQueue)) != 0)
  1482.         {(sem_post(&(cv->semBlockQueue))
  1483.           return errno;
  1484.         }
  1485.     }
  1486.   /*
  1487.    * No waiter blocked - no more LEVEL-1 access to blocked count needed...
  1488.    */
  1489.   else if (sem_post(&(cv->semBlockLock)) != 0)
  1490.     {
  1491.       return errno;
  1492.     }
  1493.   /*
  1494.    * ...and no more LEVEL-2 access to waiters (to)unblock(ed) counts needed
  1495. too
  1496.    * This sync.level supports _timedwait and cancellation
  1497.    */
  1498.   else
  1499.     {
  1500.       result = pthread_mutex_unlock(&(cv->mtxUnblockLock));
  1501.     }
  1502.  
  1503.   return(result);
  1504.  
  1505. }                               /* ptw32_cond_unblock */
  1506.  
  1507. int
  1508. pthread_cond_wait (pthread_cond_t * cond,
  1509.                    pthread_mutex_t * mutex)
  1510. {
  1511.   /* The NULL abstime arg means INFINITE waiting. */
  1512.   return(ptw32_cond_timedwait(cond, mutex, NULL));
  1513. }                               /* pthread_cond_wait */
  1514.  
  1515.  
  1516. int
  1517. pthread_cond_timedwait (pthread_cond_t * cond,
  1518.                         pthread_mutex_t * mutex,
  1519.                         const struct timespec *abstime)
  1520. {
  1521.   if (abstime == NULL)
  1522.     {(abstime
  1523.       return EINVAL;
  1524.     }
  1525.  
  1526.   return(ptw32_cond_timedwait(cond, mutex, abstime));
  1527. }                               /* pthread_cond_timedwait */
  1528.  
  1529.  
  1530. int
  1531. pthread_cond_signal (pthread_cond_t * cond)
  1532. {
  1533.   /* The '0'(FALSE) unblockAll arg means unblock ONE waiter. */
  1534.   return(ptw32_cond_unblock(cond, 0));
  1535. }                               /* pthread_cond_signal */
  1536.  
  1537. int
  1538. pthread_cond_broadcast (pthread_cond_t * cond)
  1539. {
  1540.   /* The '1'(TRUE) unblockAll arg means unblock ALL waiters. */
  1541.   return(ptw32_cond_unblock(cond, 1));
  1542. }                               /* pthread_cond_broadcast */
  1543.  
  1544.  
  1545.  
  1546.  
  1547. TEREKHOV@de.ibm.com on 17.01.2001 01:00:57
  1548.  
  1549. Please respond to TEREKHOV@de.ibm.com
  1550.  
  1551. To:   pthreads-win32@sourceware.cygnus.com
  1552. cc:   schmidt@uci.edu
  1553. Subject:  win32 conditions: sem+counter+event = broadcast_deadlock +
  1554.       spur.wakeup/unfairness/incorrectness ??
  1555.  
  1556.  
  1557.  
  1558.  
  1559.  
  1560.  
  1561.  
  1562. Hi,
  1563.  
  1564. Problem 1: broadcast_deadlock
  1565.  
  1566. It seems that current implementation does not provide "atomic"
  1567. broadcasts. That may lead to "nested" broadcasts... and it seems
  1568. that nested case is not handled correctly -> producing a broadcast
  1569. DEADLOCK as a result.
  1570.  
  1571. Scenario:
  1572.  
  1573. N (>1) waiting threads W1..N are blocked (in _wait) on condition's
  1574. semaphore.
  1575.  
  1576. Thread B1 calls pthread_cond_broadcast, which results in "releasing" N
  1577. W threads via incrementing semaphore counter by N (stored in
  1578. cv->waiters) BUT cv->waiters counter does not change!! The caller
  1579. thread B1 remains blocked on cv->waitersDone event (auto-reset!!) BUT
  1580. condition is not protected from starting another broadcast (when called
  1581. on another thread) while still waiting for the "old" broadcast to
  1582. complete on thread B1.
  1583.  
  1584. M (>=0, <N) W threads are fast enough to go thru their _wait call and
  1585. decrement cv->waiters counter.
  1586.  
  1587. L (N-M) "late" waiter W threads are a) still blocked/not returned from
  1588. their semaphore wait call or b) were preempted after sem_wait but before
  1589. lock( &cv->waitersLock ) or c) are blocked on cv->waitersLock.
  1590.  
  1591. cv->waiters is still > 0 (= L).
  1592.  
  1593. Another thread B2 (or some W thread from M group) calls
  1594. pthread_cond_broadcast and gains access to counter... neither a) nor b)
  1595. prevent thread B2 in pthread_cond_broadcast from gaining access to
  1596. counter and starting another broadcast ( for c) - it depends on
  1597. cv->waitersLock scheduling rules: FIFO=OK, PRTY=PROBLEM,... )
  1598.  
  1599. That call to pthread_cond_broadcast (on thread B2) will result in
  1600. incrementing semaphore by cv->waiters (=L) which is INCORRECT (all
  1601. W1..N were in fact already released by thread B1) and waiting on
  1602. _auto-reset_ event cv->waitersDone which is DEADLY WRONG (produces a
  1603. deadlock)...
  1604.  
  1605. All late W1..L threads now have a chance to complete their _wait call.
  1606. Last W_L thread sets an auto-reselt event cv->waitersDone which will
  1607. release either B1 or B2 leaving one of B threads in a deadlock.
  1608.  
  1609. Problem 2: spur.wakeup/unfairness/incorrectness
  1610.  
  1611. It seems that:
  1612.  
  1613. a) because of the same problem with counter which does not reflect the
  1614. actual number of NOT RELEASED waiters, the signal call may increment
  1615. a semaphore counter w/o having a waiter blocked on it. That will result
  1616. in (best case) spurious wake ups - performance degradation due to
  1617. unnecessary context switches and predicate re-checks and (in worth case)
  1618. unfairness/incorrectness problem - see b)
  1619.  
  1620. b) neither signal nor broadcast prevent other threads - "new waiters"
  1621. (and in the case of signal, the caller thread as well) from going into
  1622. _wait and overtaking "old" waiters (already released but still not returned
  1623. from sem_wait on condition's semaphore). Win semaphore just [API DOC]:
  1624. "Maintains a count between zero and some maximum value, limiting the number
  1625. of threads that are simultaneously accessing a shared resource." Calling
  1626. ReleaseSemaphore does not imply (at least not documented) that on return
  1627. from ReleaseSemaphore all waiters will in fact become released (returned
  1628. from their Wait... call) and/or that new waiters calling Wait... afterwards
  1629. will become less importance. It is NOT documented to be an atomic release
  1630. of
  1631. waiters... And even if it would be there is still a problem with a thread
  1632. being preempted after Wait on semaphore and before Wait on cv->waitersLock
  1633. and scheduling rules for cv->waitersLock itself
  1634. (??WaitForMultipleObjects??)
  1635. That may result in unfairness/incorrectness problem as described
  1636. for SetEvent impl. in "Strategies for Implementing POSIX Condition
  1637. Variables
  1638. on Win32": http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
  1639.  
  1640. Unfairness -- The semantics of the POSIX pthread_cond_broadcast function is
  1641. to wake up all threads currently blocked in wait calls on the condition
  1642. variable. The awakened threads then compete for the external_mutex. To
  1643. ensure
  1644. fairness, all of these threads should be released from their
  1645. pthread_cond_wait calls and allowed to recheck their condition expressions
  1646. before other threads can successfully complete a wait on the condition
  1647. variable.
  1648.  
  1649. Unfortunately, the SetEvent implementation above does not guarantee that
  1650. all
  1651. threads sleeping on the condition variable when cond_broadcast is called
  1652. will
  1653. acquire the external_mutex and check their condition expressions. Although
  1654. the Pthreads specification does not mandate this degree of fairness, the
  1655. lack of fairness can cause starvation.
  1656.  
  1657. To illustrate the unfairness problem, imagine there are 2 threads, C1 and
  1658. C2,
  1659. that are blocked in pthread_cond_wait on condition variable not_empty_ that
  1660. is guarding a thread-safe message queue. Another thread, P1 then places two
  1661. messages onto the queue and calls pthread_cond_broadcast. If C1 returns
  1662. from
  1663. pthread_cond_wait, dequeues and processes the message, and immediately
  1664. waits
  1665. again then it and only it may end up acquiring both messages. Thus, C2 will
  1666. never get a chance to dequeue a message and run.
  1667.  
  1668. The following illustrates the sequence of events:
  1669.  
  1670. 1.   Thread C1 attempts to dequeue and waits on CV non_empty_
  1671. 2.   Thread C2 attempts to dequeue and waits on CV non_empty_
  1672. 3.   Thread P1 enqueues 2 messages and broadcasts to CV not_empty_
  1673. 4.   Thread P1 exits
  1674. 5.   Thread C1 wakes up from CV not_empty_, dequeues a message and runs
  1675. 6.   Thread C1 waits again on CV not_empty_, immediately dequeues the 2nd
  1676.         message and runs
  1677. 7.   Thread C1 exits
  1678. 8.   Thread C2 is the only thread left and blocks forever since
  1679.         not_empty_ will never be signaled
  1680.  
  1681. Depending on the algorithm being implemented, this lack of fairness may
  1682. yield
  1683. concurrent programs that have subtle bugs. Of course, application
  1684. developers
  1685. should not rely on the fairness semantics of pthread_cond_broadcast.
  1686. However,
  1687. there are many cases where fair implementations of condition variables can
  1688. simplify application code.
  1689.  
  1690. Incorrectness -- A variation on the unfairness problem described above
  1691. occurs
  1692. when a third consumer thread, C3, is allowed to slip through even though it
  1693. was not waiting on condition variable not_empty_ when a broadcast occurred.
  1694.  
  1695. To illustrate this, we will use the same scenario as above: 2 threads, C1
  1696. and
  1697. C2, are blocked dequeuing messages from the message queue. Another thread,
  1698. P1
  1699. then places two messages onto the queue and calls pthread_cond_broadcast.
  1700. C1
  1701. returns from pthread_cond_wait, dequeues and processes the message. At this
  1702. time, C3 acquires the external_mutex, calls pthread_cond_wait and waits on
  1703. the events in WaitForMultipleObjects. Since C2 has not had a chance to run
  1704. yet, the BROADCAST event is still signaled. C3 then returns from
  1705. WaitForMultipleObjects, and dequeues and processes the message in the
  1706. queue.
  1707. Thus, C2 will never get a chance to dequeue a message and run.
  1708.  
  1709. The following illustrates the sequence of events:
  1710.  
  1711. 1.   Thread C1 attempts to dequeue and waits on CV non_empty_
  1712. 2.   Thread C2 attempts to dequeue and waits on CV non_empty_
  1713. 3.   Thread P1 enqueues 2 messages and broadcasts to CV not_empty_
  1714. 4.   Thread P1 exits
  1715. 5.   Thread C1 wakes up from CV not_empty_, dequeues a message and runs
  1716. 6.   Thread C1 exits
  1717. 7.   Thread C3 waits on CV not_empty_, immediately dequeues the 2nd
  1718.         message and runs
  1719. 8.   Thread C3 exits
  1720. 9.   Thread C2 is the only thread left and blocks forever since
  1721.         not_empty_ will never be signaled
  1722.  
  1723. In the above case, a thread that was not waiting on the condition variable
  1724. when a broadcast occurred was allowed to proceed. This leads to incorrect
  1725. semantics for a condition variable.
  1726.  
  1727.  
  1728. COMMENTS???
  1729.  
  1730. regards,
  1731. alexander.
  1732.  
  1733. -----------------------------------------------------------------------------
  1734.  
  1735. Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_*
  1736.      implementation questions
  1737. Date: Wed, 21 Feb 2001 11:54:47 +0100
  1738. From: TEREKHOV@de.ibm.com
  1739. To: lthomas@arbitrade.com
  1740. CC: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>,
  1741.      Nanbor Wang <nanbor@cs.wustl.edu>
  1742.  
  1743. Hi Louis,
  1744.  
  1745. generation number 8..
  1746.  
  1747. had some time to revisit timeouts/spurious wakeup problem..
  1748. found some bugs (in 7.b/c/d) and something to improve
  1749. (7a - using IPC semaphores but it should speedup Win32
  1750. version as well).
  1751.  
  1752. regards,
  1753. alexander.
  1754.  
  1755. ---------- Algorithm 8a / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ALL ------
  1756. given:
  1757. semBlockLock - bin.semaphore
  1758. semBlockQueue - semaphore
  1759. mtxExternal - mutex or CS
  1760. mtxUnblockLock - mutex or CS
  1761. nWaitersGone - int
  1762. nWaitersBlocked - int
  1763. nWaitersToUnblock - int
  1764.  
  1765. wait( timeout ) {
  1766.  
  1767.   [auto: register int result          ]     // error checking omitted
  1768.   [auto: register int nSignalsWasLeft ]
  1769.   [auto: register int nWaitersWasGone ]
  1770.  
  1771.   sem_wait( semBlockLock );
  1772.   nWaitersBlocked++;
  1773.   sem_post( semBlockLock );
  1774.  
  1775.   unlock( mtxExternal );
  1776.   bTimedOut = sem_wait( semBlockQueue,timeout );
  1777.  
  1778.   lock( mtxUnblockLock );
  1779.   if ( 0 != (nSignalsWasLeft = nWaitersToUnblock) ) {
  1780.     if ( bTimeout ) {                       // timeout (or canceled)
  1781.       if ( 0 != nWaitersBlocked ) {
  1782.         nWaitersBlocked--;
  1783.       }
  1784.       else {
  1785.         nWaitersGone++;                     // count spurious wakeups
  1786.       }
  1787.     }
  1788.     if ( 0 == --nWaitersToUnblock ) {
  1789.       if ( 0 != nWaitersBlocked ) {
  1790.         sem_post( semBlockLock );           // open the gate
  1791.         nSignalsWasLeft = 0;                // do not open the gate below
  1792. again
  1793.       }
  1794.       else if ( 0 != (nWaitersWasGone = nWaitersGone) ) {
  1795.         nWaitersGone = 0;
  1796.       }
  1797.     }
  1798.   }
  1799.   else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
  1800. semaphore :-)
  1801.     sem_wait( semBlockLock );
  1802.     nWaitersBlocked -= nWaitersGone;        // something is going on here -
  1803. test of timeouts? :-)
  1804.     sem_post( semBlockLock );
  1805.     nWaitersGone = 0;
  1806.   }
  1807.   unlock( mtxUnblockLock );
  1808.  
  1809.   if ( 1 == nSignalsWasLeft ) {
  1810.     if ( 0 != nWaitersWasGone ) {
  1811.       // sem_adjust( -nWaitersWasGone );
  1812.       while ( nWaitersWasGone-- ) {
  1813.         sem_wait( semBlockLock );          // better now than spurious
  1814. later
  1815.       }
  1816.     }
  1817.     sem_post( semBlockLock );              // open the gate
  1818.   }
  1819.  
  1820.   lock( mtxExternal );
  1821.  
  1822.   return ( bTimedOut ) ? ETIMEOUT : 0;
  1823. }
  1824.  
  1825. signal(bAll) {
  1826.  
  1827.   [auto: register int result         ]
  1828.   [auto: register int nSignalsToIssue]
  1829.  
  1830.   lock( mtxUnblockLock );
  1831.  
  1832.   if ( 0 != nWaitersToUnblock ) { // the gate is closed!!!
  1833.     if ( 0 == nWaitersBlocked ) { // NO-OP
  1834.       return unlock( mtxUnblockLock );
  1835.     }
  1836.     if (bAll) {
  1837.       nWaitersToUnblock += nSignalsToIssue=nWaitersBlocked;
  1838.       nWaitersBlocked = 0;
  1839.     }
  1840.     else {
  1841.       nSignalsToIssue = 1;
  1842.       nWaitersToUnblock++;
  1843.       nWaitersBlocked--;
  1844.     }
  1845.   }
  1846.   else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION!
  1847.     sem_wait( semBlockLock ); // close the gate
  1848.     if ( 0 != nWaitersGone ) {
  1849.       nWaitersBlocked -= nWaitersGone;
  1850.       nWaitersGone = 0;
  1851.     }
  1852.     if (bAll) {
  1853.       nSignalsToIssue = nWaitersToUnblock = nWaitersBlocked;
  1854.       nWaitersBlocked = 0;
  1855.     }
  1856.     else {
  1857.       nSignalsToIssue = nWaitersToUnblock = 1;
  1858.       nWaitersBlocked--;
  1859.     }
  1860.   }
  1861.   else { // NO-OP
  1862.     return unlock( mtxUnblockLock );
  1863.   }
  1864.  
  1865.   unlock( mtxUnblockLock );
  1866.   sem_post( semBlockQueue,nSignalsToIssue );
  1867.   return result;
  1868. }
  1869.  
  1870. ---------- Algorithm 8b / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ONEBYONE
  1871. ------
  1872. given:
  1873. semBlockLock - bin.semaphore
  1874. semBlockQueue - bin.semaphore
  1875. mtxExternal - mutex or CS
  1876. mtxUnblockLock - mutex or CS
  1877. nWaitersGone - int
  1878. nWaitersBlocked - int
  1879. nWaitersToUnblock - int
  1880.  
  1881. wait( timeout ) {
  1882.  
  1883.   [auto: register int result          ]     // error checking omitted
  1884.   [auto: register int nWaitersWasGone ]
  1885.   [auto: register int nSignalsWasLeft ]
  1886.  
  1887.   sem_wait( semBlockLock );
  1888.   nWaitersBlocked++;
  1889.   sem_post( semBlockLock );
  1890.  
  1891.   unlock( mtxExternal );
  1892.   bTimedOut = sem_wait( semBlockQueue,timeout );
  1893.  
  1894.   lock( mtxUnblockLock );
  1895.   if ( 0 != (nSignalsWasLeft = nWaitersToUnblock) ) {
  1896.     if ( bTimeout ) {                       // timeout (or canceled)
  1897.       if ( 0 != nWaitersBlocked ) {
  1898.         nWaitersBlocked--;
  1899.         nSignalsWasLeft = 0;                // do not unblock next waiter
  1900. below (already unblocked)
  1901.       }
  1902.       else {
  1903.         nWaitersGone = 1;                   // spurious wakeup pending!!
  1904.       }
  1905.     }
  1906.     if ( 0 == --nWaitersToUnblock &&
  1907.       if ( 0 != nWaitersBlocked ) {
  1908.         sem_post( semBlockLock );           // open the gate
  1909.         nSignalsWasLeft = 0;                // do not open the gate below
  1910. again
  1911.       }
  1912.       else if ( 0 != (nWaitersWasGone = nWaitersGone) ) {
  1913.         nWaitersGone = 0;
  1914.       }
  1915.     }
  1916.   }
  1917.   else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
  1918. semaphore :-)
  1919.     sem_wait( semBlockLock );
  1920.     nWaitersBlocked -= nWaitersGone;        // something is going on here -
  1921. test of timeouts? :-)
  1922.     sem_post( semBlockLock );
  1923.     nWaitersGone = 0;
  1924.   }
  1925.   unlock( mtxUnblockLock );
  1926.  
  1927.   if ( 1 == nSignalsWasLeft ) {
  1928.     if ( 0 != nWaitersWasGone ) {
  1929.       // sem_adjust( -1 );
  1930.       sem_wait( semBlockQueue );           // better now than spurious
  1931. later
  1932.     }
  1933.     sem_post( semBlockLock );              // open the gate
  1934.   }
  1935.   else if ( 0 != nSignalsWasLeft ) {
  1936.     sem_post( semBlockQueue );             // unblock next waiter
  1937.   }
  1938.  
  1939.   lock( mtxExternal );
  1940.  
  1941.   return ( bTimedOut ) ? ETIMEOUT : 0;
  1942. }
  1943.  
  1944. signal(bAll) {
  1945.  
  1946.   [auto: register int result ]
  1947.  
  1948.   lock( mtxUnblockLock );
  1949.  
  1950.   if ( 0 != nWaitersToUnblock ) { // the gate is closed!!!
  1951.     if ( 0 == nWaitersBlocked ) { // NO-OP
  1952.       return unlock( mtxUnblockLock );
  1953.     }
  1954.     if (bAll) {
  1955.       nWaitersToUnblock += nWaitersBlocked;
  1956.       nWaitersBlocked = 0;
  1957.     }
  1958.     else {
  1959.       nWaitersToUnblock++;
  1960.       nWaitersBlocked--;
  1961.     }
  1962.     unlock( mtxUnblockLock );
  1963.   }
  1964.   else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION!
  1965.     sem_wait( semBlockLock ); // close the gate
  1966.     if ( 0 != nWaitersGone ) {
  1967.       nWaitersBlocked -= nWaitersGone;
  1968.       nWaitersGone = 0;
  1969.     }
  1970.     if (bAll) {
  1971.       nWaitersToUnblock = nWaitersBlocked;
  1972.       nWaitersBlocked = 0;
  1973.     }
  1974.     else {
  1975.       nWaitersToUnblock = 1;
  1976.       nWaitersBlocked--;
  1977.     }
  1978.     unlock( mtxUnblockLock );
  1979.     sem_post( semBlockQueue );
  1980.   }
  1981.   else { // NO-OP
  1982.     unlock( mtxUnblockLock );
  1983.   }
  1984.  
  1985.   return result;
  1986. }
  1987.  
  1988. ---------- Algorithm 8c / IMPL_EVENT,UNBLOCK_STRATEGY == UNBLOCK_ONEBYONE
  1989. ---------
  1990. given:
  1991. hevBlockLock - auto-reset event
  1992. hevBlockQueue - auto-reset event
  1993. mtxExternal - mutex or CS
  1994. mtxUnblockLock - mutex or CS
  1995. nWaitersGone - int
  1996. nWaitersBlocked - int
  1997. nWaitersToUnblock - int
  1998.  
  1999. wait( timeout ) {
  2000.  
  2001.   [auto: register int result          ]     // error checking omitted
  2002.   [auto: register int nSignalsWasLeft ]
  2003.   [auto: register int nWaitersWasGone ]
  2004.  
  2005.   wait( hevBlockLock,INFINITE );
  2006.   nWaitersBlocked++;
  2007.   set_event( hevBlockLock );
  2008.  
  2009.   unlock( mtxExternal );
  2010.   bTimedOut = wait( hevBlockQueue,timeout );
  2011.  
  2012.   lock( mtxUnblockLock );
  2013.   if ( 0 != (SignalsWasLeft = nWaitersToUnblock) ) {
  2014.     if ( bTimeout ) {                       // timeout (or canceled)
  2015.       if ( 0 != nWaitersBlocked ) {
  2016.         nWaitersBlocked--;
  2017.         nSignalsWasLeft = 0;                // do not unblock next waiter
  2018. below (already unblocked)
  2019.       }
  2020.       else {
  2021.         nWaitersGone = 1;                   // spurious wakeup pending!!
  2022.       }
  2023.     }
  2024.     if ( 0 == --nWaitersToUnblock )
  2025.       if ( 0 != nWaitersBlocked ) {
  2026.         set_event( hevBlockLock );          // open the gate
  2027.         nSignalsWasLeft = 0;                // do not open the gate below
  2028. again
  2029.       }
  2030.       else if ( 0 != (nWaitersWasGone = nWaitersGone) ) {
  2031.         nWaitersGone = 0;
  2032.       }
  2033.     }
  2034.   }
  2035.   else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
  2036. event :-)
  2037.     wait( hevBlockLock,INFINITE );
  2038.     nWaitersBlocked -= nWaitersGone;        // something is going on here -
  2039. test of timeouts? :-)
  2040.     set_event( hevBlockLock );
  2041.     nWaitersGone = 0;
  2042.   }
  2043.   unlock( mtxUnblockLock );
  2044.  
  2045.   if ( 1 == nSignalsWasLeft ) {
  2046.     if ( 0 != nWaitersWasGone ) {
  2047.       reset_event( hevBlockQueue );         // better now than spurious
  2048. later
  2049.     }
  2050.     set_event( hevBlockLock );              // open the gate
  2051.   }
  2052.   else if ( 0 != nSignalsWasLeft ) {
  2053.     set_event( hevBlockQueue );             // unblock next waiter
  2054.   }
  2055.  
  2056.   lock( mtxExternal );
  2057.  
  2058.   return ( bTimedOut ) ? ETIMEOUT : 0;
  2059. }
  2060.  
  2061. signal(bAll) {
  2062.  
  2063.   [auto: register int result ]
  2064.  
  2065.   lock( mtxUnblockLock );
  2066.  
  2067.   if ( 0 != nWaitersToUnblock ) { // the gate is closed!!!
  2068.     if ( 0 == nWaitersBlocked ) { // NO-OP
  2069.       return unlock( mtxUnblockLock );
  2070.     }
  2071.     if (bAll) {
  2072.       nWaitersToUnblock += nWaitersBlocked;
  2073.       nWaitersBlocked = 0;
  2074.     }
  2075.     else {
  2076.       nWaitersToUnblock++;
  2077.       nWaitersBlocked--;
  2078.     }
  2079.     unlock( mtxUnblockLock );
  2080.   }
  2081.   else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION!
  2082.     wait( hevBlockLock,INFINITE ); // close the gate
  2083.     if ( 0 != nWaitersGone ) {
  2084.       nWaitersBlocked -= nWaitersGone;
  2085.       nWaitersGone = 0;
  2086.     }
  2087.     if (bAll) {
  2088.       nWaitersToUnblock = nWaitersBlocked;
  2089.       nWaitersBlocked = 0;
  2090.     }
  2091.     else {
  2092.       nWaitersToUnblock = 1;
  2093.       nWaitersBlocked--;
  2094.     }
  2095.     unlock( mtxUnblockLock );
  2096.     set_event( hevBlockQueue );
  2097.   }
  2098.   else { // NO-OP
  2099.     unlock( mtxUnblockLock );
  2100.   }
  2101.  
  2102.   return result;
  2103. }
  2104.  
  2105. ---------- Algorithm 8d / IMPL_EVENT,UNBLOCK_STRATEGY == UNBLOCK_ALL ------
  2106. given:
  2107. hevBlockLock - auto-reset event
  2108. hevBlockQueueS - auto-reset event  // for signals
  2109. hevBlockQueueB - manual-reset even // for broadcasts
  2110. mtxExternal - mutex or CS
  2111. mtxUnblockLock - mutex or CS
  2112. eBroadcast - int                   // 0: no broadcast, 1: broadcast, 2:
  2113. broadcast after signal(s)
  2114. nWaitersGone - int
  2115. nWaitersBlocked - int
  2116. nWaitersToUnblock - int
  2117.  
  2118. wait( timeout ) {
  2119.  
  2120.   [auto: register int result          ]     // error checking omitted
  2121.   [auto: register int eWasBroadcast   ]
  2122.   [auto: register int nSignalsWasLeft ]
  2123.   [auto: register int nWaitersWasGone ]
  2124.  
  2125.   wait( hevBlockLock,INFINITE );
  2126.   nWaitersBlocked++;
  2127.   set_event( hevBlockLock );
  2128.  
  2129.   unlock( mtxExternal );
  2130.   bTimedOut = waitformultiple( hevBlockQueueS,hevBlockQueueB,timeout,ONE );
  2131.  
  2132.   lock( mtxUnblockLock );
  2133.   if ( 0 != (SignalsWasLeft = nWaitersToUnblock) ) {
  2134.     if ( bTimeout ) {                       // timeout (or canceled)
  2135.       if ( 0 != nWaitersBlocked ) {
  2136.         nWaitersBlocked--;
  2137.         nSignalsWasLeft = 0;                // do not unblock next waiter
  2138. below (already unblocked)
  2139.       }
  2140.       else if ( 1 != eBroadcast ) {
  2141.         nWaitersGone = 1;
  2142.       }
  2143.     }
  2144.     if ( 0 == --nWaitersToUnblock ) {
  2145.       if ( 0 != nWaitersBlocked ) {
  2146.         set_event( hevBlockLock );           // open the gate
  2147.         nSignalsWasLeft = 0;                 // do not open the gate below
  2148. again
  2149.       }
  2150.       else {
  2151.         if ( 0 != (eWasBroadcast = eBroadcast) ) {
  2152.           eBroadcast = 0;
  2153.         }
  2154.         if ( 0 != (nWaitersWasGone = nWaitersGone ) {
  2155.           nWaitersGone = 0;
  2156.         }
  2157.       }
  2158.     }
  2159.     else if ( 0 != eBroadcast ) {
  2160.       nSignalsWasLeft = 0;                  // do not unblock next waiter
  2161. below (already unblocked)
  2162.     }
  2163.   }
  2164.   else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
  2165. event :-)
  2166.     wait( hevBlockLock,INFINITE );
  2167.     nWaitersBlocked -= nWaitersGone;        // something is going on here -
  2168. test of timeouts? :-)
  2169.     set_event( hevBlockLock );
  2170.     nWaitersGone = 0;
  2171.   }
  2172.   unlock( mtxUnblockLock );
  2173.  
  2174.   if ( 1 == nSignalsWasLeft ) {
  2175.     if ( 0 != eWasBroadcast ) {
  2176.       reset_event( hevBlockQueueB );
  2177.     }
  2178.     if ( 0 != nWaitersWasGone ) {
  2179.       reset_event( hevBlockQueueS );        // better now than spurious
  2180. later
  2181.     }
  2182.     set_event( hevBlockLock );              // open the gate
  2183.   }
  2184.   else if ( 0 != nSignalsWasLeft ) {
  2185.     set_event( hevBlockQueueS );            // unblock next waiter
  2186.   }
  2187.  
  2188.   lock( mtxExternal );
  2189.  
  2190.   return ( bTimedOut ) ? ETIMEOUT : 0;
  2191. }
  2192.  
  2193. signal(bAll) {
  2194.  
  2195.   [auto: register int    result        ]
  2196.   [auto: register HANDLE hevBlockQueue ]
  2197.  
  2198.   lock( mtxUnblockLock );
  2199.  
  2200.   if ( 0 != nWaitersToUnblock ) { // the gate is closed!!!
  2201.     if ( 0 == nWaitersBlocked ) { // NO-OP
  2202.       return unlock( mtxUnblockLock );
  2203.     }
  2204.     if (bAll) {
  2205.       nWaitersToUnblock += nWaitersBlocked;
  2206.       nWaitersBlocked = 0;
  2207.       eBroadcast = 2;
  2208.       hevBlockQueue = hevBlockQueueB;
  2209.     }
  2210.     else {
  2211.       nWaitersToUnblock++;
  2212.       nWaitersBlocked--;
  2213.       return unlock( mtxUnblockLock );
  2214.     }
  2215.   }
  2216.   else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION!
  2217.     wait( hevBlockLock,INFINITE ); // close the gate
  2218.     if ( 0 != nWaitersGone ) {
  2219.       nWaitersBlocked -= nWaitersGone;
  2220.       nWaitersGone = 0;
  2221.     }
  2222.     if (bAll) {
  2223.       nWaitersToUnblock = nWaitersBlocked;
  2224.       nWaitersBlocked = 0;
  2225.       eBroadcast = 1;
  2226.       hevBlockQueue = hevBlockQueueB;
  2227.     }
  2228.     else {
  2229.       nWaitersToUnblock = 1;
  2230.       nWaitersBlocked--;
  2231.       hevBlockQueue = hevBlockQueueS;
  2232.     }
  2233.   }
  2234.   else { // NO-OP
  2235.     return unlock( mtxUnblockLock );
  2236.   }
  2237.  
  2238.   unlock( mtxUnblockLock );
  2239.   set_event( hevBlockQueue );
  2240.   return result;
  2241. }
  2242. ---------------------- Forwarded by Alexander Terekhov/Germany/IBM on
  2243. 02/21/2001 09:13 AM ---------------------------
  2244.  
  2245. Alexander Terekhov
  2246. 02/20/2001 04:33 PM
  2247.  
  2248. To:   Louis Thomas <lthomas@arbitrade.com>
  2249. cc:
  2250.  
  2251. From: Alexander Terekhov/Germany/IBM@IBMDE
  2252. Subject:  RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
  2253.       n questions
  2254. Importance:    Normal
  2255.  
  2256. >Sorry, gotta take a break and work on something else for a while.
  2257. >Real work
  2258. >calls, unfortunately. I'll get back to you in two or three days.
  2259.  
  2260. ok. no problem. here is some more stuff for pauses you might have
  2261. in between :)
  2262.  
  2263. ---------- Algorithm 7d / IMPL_EVENT,UNBLOCK_STRATEGY == UNBLOCK_ALL ------
  2264. given:
  2265. hevBlockLock - auto-reset event
  2266. hevBlockQueueS - auto-reset event  // for signals
  2267. hevBlockQueueB - manual-reset even // for broadcasts
  2268. mtxExternal - mutex or CS
  2269. mtxUnblockLock - mutex or CS
  2270. bBroadcast - int
  2271. nWaitersGone - int
  2272. nWaitersBlocked - int
  2273. nWaitersToUnblock - int
  2274.  
  2275. wait( timeout ) {
  2276.  
  2277.   [auto: register int result          ]     // error checking omitted
  2278.   [auto: register int bWasBroadcast   ]
  2279.   [auto: register int nSignalsWasLeft ]
  2280.  
  2281.   wait( hevBlockLock,INFINITE );
  2282.   nWaitersBlocked++;
  2283.   set_event( hevBlockLock );
  2284.  
  2285.   unlock( mtxExternal );
  2286.   bTimedOut = waitformultiple( hevBlockQueueS,hevBlockQueueB,timeout,ONE );
  2287.  
  2288.   lock( mtxUnblockLock );
  2289.   if ( 0 != (SignalsWasLeft = nWaitersToUnblock) ) {
  2290.     if ( bTimeout ) {                       // timeout (or canceled)
  2291.       if ( 0 != nWaitersBlocked ) {
  2292.         nWaitersBlocked--;
  2293.         nSignalsWasLeft = 0;                // do not unblock next waiter
  2294. below (already unblocked)
  2295.       }
  2296.       else if ( !bBroadcast ) {
  2297.         wait( hevBlockQueueS,INFINITE );    // better now than spurious
  2298. later
  2299.       }
  2300.     }
  2301.     if ( 0 == --nWaitersToUnblock ) {
  2302.       if ( 0 != nWaitersBlocked ) {
  2303.         if ( bBroadcast ) {
  2304.           reset_event( hevBlockQueueB );
  2305.           bBroadcast = false;
  2306.         }
  2307.         set_event( hevBlockLock );           // open the gate
  2308.         nSignalsWasLeft = 0;                 // do not open the gate below
  2309. again
  2310.       }
  2311.       else if ( false != (bWasBroadcast = bBroadcast) ) {
  2312.         bBroadcast = false;
  2313.       }
  2314.     }
  2315.     else {
  2316.       bWasBroadcast = bBroadcast;
  2317.     }
  2318.   }
  2319.   else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
  2320. event :-)
  2321.     wait( hevBlockLock,INFINITE );
  2322.     nWaitersBlocked -= nWaitersGone;        // something is going on here -
  2323. test of timeouts? :-)
  2324.     set_event( hevBlockLock );
  2325.     nWaitersGone = 0;
  2326.   }
  2327.   unlock( mtxUnblockLock );
  2328.  
  2329.   if ( 1 == nSignalsWasLeft ) {
  2330.     if ( bWasBroadcast ) {
  2331.       reset_event( hevBlockQueueB );
  2332.     }
  2333.     set_event( hevBlockLock );              // open the gate
  2334.   }
  2335.   else if ( 0 != nSignalsWasLeft && !bWasBroadcast ) {
  2336.     set_event( hevBlockQueueS );            // unblock next waiter
  2337.   }
  2338.  
  2339.   lock( mtxExternal );
  2340.  
  2341.   return ( bTimedOut ) ? ETIMEOUT : 0;
  2342. }
  2343.  
  2344. signal(bAll) {
  2345.  
  2346.   [auto: register int    result        ]
  2347.   [auto: register HANDLE hevBlockQueue ]
  2348.  
  2349.   lock( mtxUnblockLock );
  2350.  
  2351.   if ( 0 != nWaitersToUnblock ) { // the gate is closed!!!
  2352.     if ( 0 == nWaitersBlocked ) { // NO-OP
  2353.       return unlock( mtxUnblockLock );
  2354.     }
  2355.     if (bAll) {
  2356.       nWaitersToUnblock += nWaitersBlocked;
  2357.       nWaitersBlocked = 0;
  2358.       bBroadcast = true;
  2359.       hevBlockQueue = hevBlockQueueB;
  2360.     }
  2361.     else {
  2362.       nWaitersToUnblock++;
  2363.       nWaitersBlocked--;
  2364.       return unlock( mtxUnblockLock );
  2365.     }
  2366.   }
  2367.   else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION!
  2368.     wait( hevBlockLock,INFINITE ); // close the gate
  2369.     if ( 0 != nWaitersGone ) {
  2370.       nWaitersBlocked -= nWaitersGone;
  2371.       nWaitersGone = 0;
  2372.     }
  2373.     if (bAll) {
  2374.       nWaitersToUnblock = nWaitersBlocked;
  2375.       nWaitersBlocked = 0;
  2376.       bBroadcast = true;
  2377.       hevBlockQueue = hevBlockQueueB;
  2378.     }
  2379.     else {
  2380.       nWaitersToUnblock = 1;
  2381.       nWaitersBlocked--;
  2382.       hevBlockQueue = hevBlockQueueS;
  2383.     }
  2384.   }
  2385.   else { // NO-OP
  2386.     return unlock( mtxUnblockLock );
  2387.   }
  2388.  
  2389.   unlock( mtxUnblockLock );
  2390.   set_event( hevBlockQueue );
  2391.   return result;
  2392. }
  2393.  
  2394.  
  2395. ----------------------------------------------------------------------------
  2396.  
  2397. Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
  2398.      n questions
  2399. Date: Mon, 26 Feb 2001 22:20:12 -0600
  2400. From: Louis Thomas <lthomas@arbitrade.com>
  2401. To: "'TEREKHOV@de.ibm.com'" <TEREKHOV@de.ibm.com>
  2402. CC: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>,
  2403.      Nanbor Wang
  2404.      <nanbor@cs.wustl.edu>
  2405.  
  2406. Sorry all. Busy week.
  2407.  
  2408. > this insures the fairness
  2409. > which POSIX does not (e.g. two subsequent broadcasts - the gate does
  2410. insure
  2411. > that first wave waiters will start the race for the mutex before waiters
  2412. > from the second wave - Linux pthreads process/unblock both waves
  2413. > concurrently...)
  2414.  
  2415. I'm not sure how we are any more fair about this than Linux. We certainly
  2416. don't guarantee that the threads released by the first broadcast will get
  2417. the external mutex before the threads of the second wave. In fact, it is
  2418. possible that those threads will never get the external mutex if there is
  2419. enough contention for it.
  2420.  
  2421. > e.g. i was thinking about implementation with a pool of
  2422. > N semaphores/counters [...]
  2423.  
  2424. I considered that too. The problem is as you mentioned in a). You really
  2425. need to assign threads to semaphores once you know how you want to wake them
  2426. up, not when they first begin waiting which is the only time you can assign
  2427. them.
  2428.  
  2429. > well, i am not quite sure that i've fully understood your scenario,
  2430.  
  2431. Hmm. Well, it think it's an important example, so I'll try again. First, we
  2432. have thread A which we KNOW is waiting on a condition. As soon as it becomes
  2433. unblocked for any reason, we will know because it will set a flag. Since the
  2434. flag is not set, we are 100% confident that thread A is waiting on the
  2435. condition. We have another thread, thread B, which has acquired the mutex
  2436. and is about to wait on the condition. Thus it is pretty clear that at any
  2437. point, either just A is waiting, or A and B are waiting. Now thread C comes
  2438. along. C is about to do a broadcast on the condition. A broadcast is
  2439. guaranteed to unblock all threads currently waiting on a condition, right?
  2440. Again, we said that either just A is waiting, or A and B are both waiting.
  2441. So, when C does its broadcast, depending upon whether B has started waiting
  2442. or not, thread C will unblock A or unblock A and B. Either way, C must
  2443. unblock A, right?
  2444.  
  2445. Now, you said anything that happens is correct so long as a) "a signal is
  2446. not lost between unlocking the mutex and waiting on the condition" and b) "a
  2447. thread must not steal a signal it sent", correct? Requirement b) is easy to
  2448. satisfy: in this scenario, thread C will never wait on the condition, so it
  2449. won't steal any signals.  Requirement a) is not hard either. The only way we
  2450. could fail to meet requirement a) in this scenario is if thread B was
  2451. started waiting but didn't wake up because a signal was lost. This will not
  2452. happen.
  2453.  
  2454. Now, here is what happens. Assume thread C beats thread B. Thread C looks to
  2455. see how many threads are waiting on the condition. Thread C sees just one
  2456. thread, thread A, waiting. It does a broadcast waking up just one thread
  2457. because just one thread is waiting. Next, before A can become unblocked,
  2458. thread B begins waiting. Now there are two threads waiting, but only one
  2459. will be unblocked. Suppose B wins. B will become unblocked. A will not
  2460. become unblocked, because C only unblocked one thread (sema_post cond, 1).
  2461. So at the end, B finishes and A remains blocked.
  2462.  
  2463. We have met both of your requirements, so by your rules, this is an
  2464. acceptable outcome. However, I think that the spec says this is an
  2465. unacceptable outcome! We know for certain that A was waiting and that C did
  2466. a broadcast, but A did not become unblocked! Yet, the spec says that a
  2467. broadcast wakes up all waiting threads. This did not happen. Do you agree
  2468. that this shows your rules are not strict enough?
  2469.  
  2470. > and what about N2? :) this one does allow almost everything.
  2471.  
  2472. Don't get me started about rule #2. I'll NEVER advocate an algorithm that
  2473. uses rule 2 as an excuse to suck!
  2474.  
  2475. > but it is done (decrement)under mutex protection - this is not a subject
  2476. > of a race condition.
  2477.  
  2478. You are correct. My mistake.
  2479.  
  2480. > i would remove "_bTimedOut=false".. after all, it was a real timeout..
  2481.  
  2482. I disagree. A thread that can't successfully retract its waiter status can't
  2483. really have timed out. If a thread can't return without executing extra code
  2484. to deal with the fact that someone tried to unblock it, I think it is a poor
  2485. idea to pretend we
  2486. didn't realize someone was trying to signal us. After all, a signal is more
  2487. important than a time out.
  2488.  
  2489. > when nSignaled != 0, it is possible to update nWaiters (--) and do not
  2490. > touch nGone
  2491.  
  2492. I realize this, but I was thinking that writing it the other ways saves
  2493. another if statement.
  2494.  
  2495. > adjust only if nGone != 0 and save one cache memory write - probably much
  2496. slower than 'if'
  2497.  
  2498. Hmm. You are probably right.
  2499.  
  2500. > well, in a strange (e.g. timeout test) program you may (theoretically)
  2501. > have an overflow of nWaiters/nGone counters (with waiters repeatedly
  2502. timing
  2503. > out and no signals at all).
  2504.  
  2505. Also true. Not only that, but you also have the possibility that one could
  2506. overflow the number of waiters as well! However, considering the limit you
  2507. have chosen for nWaitersGone, I suppose it is unlikely that anyone would be
  2508. able to get INT_MAX/2 threads waiting on a single condition. :)
  2509.  
  2510. Analysis of 8a:
  2511.  
  2512. It looks correct to me.
  2513.  
  2514. What are IPC semaphores?
  2515.  
  2516. In the line where you state, "else if ( nWaitersBlocked > nWaitersGone ) {
  2517. // HARMLESS RACE CONDITION!" there is no race condition for nWaitersGone
  2518. because nWaitersGone is never modified without holding mtxUnblockLock. You
  2519. are correct that there is a harmless race on nWaitersBlocked, which can
  2520. increase and make the condition become true just after we check it. If this
  2521. happens, we interpret it as the wait starting after the signal.
  2522.  
  2523. I like your optimization of this. You could improve Alg. 6 as follows:
  2524. ---------- Algorithm 6b ----------
  2525. signal(bAll) {
  2526.   _nSig=0
  2527.   lock counters
  2528.   // this is safe because nWaiting can only be decremented by a thread that
  2529.   // owns counters and nGone can only be changed by a thread that owns
  2530. counters.
  2531.   if (nWaiting>nGone) {
  2532.     if (0==nSignaled) {
  2533.       sema_wait gate // close gate if not already closed
  2534.     }
  2535.     if (nGone>0) {
  2536.       nWaiting-=nGone
  2537.       nGone=0
  2538.     }
  2539.     _nSig=bAll?nWaiting:1
  2540.     nSignaled+=_nSig
  2541.     nWaiting-=_nSig
  2542.   }
  2543.   unlock counters
  2544.   if (0!=_nSig) {
  2545.     sema_post queue, _nSig
  2546.   }
  2547. }
  2548. ---------- ---------- ----------
  2549. I guess this wouldn't apply to Alg 8a because nWaitersGone changes meanings
  2550. depending upon whether the gate is open or closed.
  2551.  
  2552. In the loop "while ( nWaitersWasGone-- ) {" you do a sema_wait on
  2553. semBlockLock. Perhaps waiting on semBlockQueue would be a better idea.
  2554.  
  2555. What have you gained by making the last thread to be signaled do the waits
  2556. for all the timed out threads, besides added complexity? It took me a long
  2557. time to figure out what your objective was with this, to realize you were
  2558. using nWaitersGone to mean two different things, and to verify that you
  2559. hadn't introduced any bug by doing this. Even now I'm not 100% sure.
  2560.  
  2561. What has all this playing about with nWaitersGone really gained us besides a
  2562. lot of complexity (it is much harder to verify that this solution is
  2563. correct), execution overhead (we now have a lot more if statements to
  2564. evaluate), and space overhead (more space for the extra code, and another
  2565. integer in our data)? We did manage to save a lock/unlock pair in an
  2566. uncommon case (when a time out occurs) at the above mentioned expenses in
  2567. the common cases.
  2568.  
  2569. As for 8b, c, and d, they look ok though I haven't studied them thoroughly.
  2570. What would you use them for?
  2571.  
  2572.     Later,
  2573.         -Louis! :)
  2574.  
  2575. -----------------------------------------------------------------------------
  2576.  
  2577. Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
  2578.      n questions
  2579. Date: Tue, 27 Feb 2001 15:51:28 +0100
  2580. From: TEREKHOV@de.ibm.com
  2581. To: Louis Thomas <lthomas@arbitrade.com>
  2582. CC: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>,
  2583.      Nanbor Wang <nanbor@cs.wustl.edu>
  2584.  
  2585. Hi Louis,
  2586.  
  2587. >> that first wave waiters will start the race for the mutex before waiters
  2588. >> from the second wave - Linux pthreads process/unblock both waves
  2589. >> concurrently...)
  2590. >
  2591. >I'm not sure how we are any more fair about this than Linux. We certainly
  2592. >don't guarantee that the threads released by the first broadcast will get
  2593. >the external mutex before the threads of the second wave. In fact, it is
  2594. >possible that those threads will never get the external mutex if there is
  2595. >enough contention for it.
  2596.  
  2597. correct. but gate is nevertheless more fair than Linux because of the
  2598. barrier it establishes between two races (1st and 2nd wave waiters) for
  2599. the mutex which under 'normal' circumstances (e.g. all threads of equal
  2600. priorities,..) will 'probably' result in fair behaviour with respect to
  2601. mutex ownership.
  2602.  
  2603. >> well, i am not quite sure that i've fully understood your scenario,
  2604. >
  2605. >Hmm. Well, it think it's an important example, so I'll try again. ...
  2606.  
  2607. ok. now i seem to understand this example. well, now it seems to me
  2608. that the only meaningful rule is just:
  2609.  
  2610. a) "a signal is not lost between unlocking the mutex and waiting on the
  2611. condition"
  2612.  
  2613. and that the rule
  2614.  
  2615. b) "a thread must not steal a signal it sent"
  2616.  
  2617. is not needed at all because a thread which violates b) also violates a).
  2618.  
  2619. i'll try to explain..
  2620.  
  2621. i think that the most important thing is how POSIX defines waiter's
  2622. visibility:
  2623.  
  2624. "if another thread is able to acquire the mutex after the about-to-block
  2625. thread
  2626. has released it, then a subsequent call to pthread_cond_signal() or
  2627. pthread_cond_broadcast() in that thread behaves as if it were issued after
  2628. the about-to-block thread has blocked. "
  2629.  
  2630. my understanding is the following:
  2631.  
  2632. 1) there is no guarantees whatsoever with respect to whether
  2633. signal/broadcast
  2634. will actually unblock any 'waiter' if it is done w/o acquiring the mutex
  2635. first
  2636. (note that a thread may release it before signal/broadcast - it does not
  2637. matter).
  2638.  
  2639. 2) it is guaranteed that waiters become 'visible' - eligible for unblock as
  2640. soon
  2641. as signalling thread acquires the mutex (but not before!!)
  2642.  
  2643. so..
  2644.  
  2645. >So, when C does its broadcast, depending upon whether B has started
  2646. waiting
  2647. >or not, thread C will unblock A or unblock A and B. Either way, C must
  2648. >unblock A, right?
  2649.  
  2650. right. but only if C did acquire the mutex prior to broadcast (it may
  2651. release it before broadcast as well).
  2652.  
  2653. implementation will violate waiters visibility rule (signal will become
  2654. lost)
  2655. if C will not unblock A.
  2656.  
  2657. >Now, here is what happens. Assume thread C beats thread B. Thread C looks
  2658. to
  2659. >see how many threads are waiting on the condition. Thread C sees just one
  2660. >thread, thread A, waiting. It does a broadcast waking up just one thread
  2661. >because just one thread is waiting. Next, before A can become unblocked,
  2662. >thread B begins waiting. Now there are two threads waiting, but only one
  2663. >will be unblocked. Suppose B wins. B will become unblocked. A will not
  2664. >become unblocked, because C only unblocked one thread (sema_post cond, 1).
  2665. >So at the end, B finishes and A remains blocked.
  2666.  
  2667. thread C did acquire the mutex ("Thread C sees just one thread, thread A,
  2668. waiting"). beginning from that moment it is guaranteed that subsequent
  2669. broadcast will unblock A. Otherwise we will have a lost signal with respect
  2670. to A. I do think that it does not matter whether the signal was physically
  2671. (completely) lost or was just stolen by another thread (B) - in both cases
  2672. it was simply lost with respect to A.
  2673.  
  2674. >..Do you agree that this shows your rules are not strict enough?
  2675.  
  2676. probably the opposite.. :-) i think that it shows that the only meaningful
  2677. rule is
  2678.  
  2679. a) "a signal is not lost between unlocking the mutex and waiting on the
  2680. condition"
  2681.  
  2682. with clarification of waiters visibility as defined by POSIX above.
  2683.  
  2684. >> i would remove "_bTimedOut=false".. after all, it was a real timeout..
  2685. >
  2686. >I disagree. A thread that can't successfully retract its waiter status
  2687. can't
  2688. >really have timed out. If a thread can't return without executing extra
  2689. code
  2690. >to deal with the fact that someone tried to unblock it, I think it is a
  2691. poor
  2692. >idea to pretend we
  2693. >didn't realize someone was trying to signal us. After all, a signal is
  2694. more
  2695. >important than a time out.
  2696.  
  2697. a) POSIX does allow timed out thread to consume a signal (cancelled is
  2698. not).
  2699. b) ETIMEDOUT status just says that: "The time specified by abstime to
  2700. pthread_cond_timedwait() has passed."
  2701. c) it seem to me that hiding timeouts would violate "The
  2702. pthread_cond_timedwait()
  2703. function is the same as pthread_cond_wait() except that an error is
  2704. returned if
  2705. the absolute time specified by abstime passes (that is, system time equals
  2706. or
  2707. exceeds abstime) before the condition cond is signaled or broadcasted"
  2708. because
  2709. the abs. time did really pass before cond was signaled (waiter was
  2710. released via semaphore). however, if it really matters, i could imaging
  2711. that we
  2712. can save an abs. time of signal/broadcast and compare it with timeout after
  2713. unblock to find out whether it was a 'real' timeout or not. absent this
  2714. check
  2715. i do think that hiding timeouts would result in technical violation of
  2716. specification.. but i think that this check is not important and we can
  2717. simply
  2718. trust timeout error code provided by wait since we are not trying to make
  2719. 'hard' realtime implementation.
  2720.  
  2721. >What are IPC semaphores?
  2722.  
  2723. <sys/sem.h>
  2724. int   semctl(int, int, int, ...);
  2725. int   semget(key_t, int, int);
  2726. int   semop(int, struct sembuf *, size_t);
  2727.  
  2728. they support adjustment of semaphore counter (semvalue)
  2729. in one single call - imaging Win32 ReleaseSemaphore( hsem,-N )
  2730.  
  2731. >In the line where you state, "else if ( nWaitersBlocked > nWaitersGone ) {
  2732. >// HARMLESS RACE CONDITION!" there is no race condition for nWaitersGone
  2733. >because nWaitersGone is never modified without holding mtxUnblockLock. You
  2734. >are correct that there is a harmless race on nWaitersBlocked, which can
  2735. >increase and make the condition become true just after we check it. If
  2736. this
  2737. >happens, we interpret it as the wait starting after the signal.
  2738.  
  2739. well, the reason why i've asked on comp.programming.threads whether this
  2740. race
  2741. condition is harmless or not is that in order to be harmless it should not
  2742. violate the waiters visibility rule (see above). Fortunately, we increment
  2743. the counter under protection of external mutex.. so that any (signalling)
  2744. thread which will acquire the mutex next, should see the updated counter
  2745. (in signal) according to POSIX memory visibility rules and mutexes
  2746. (memory barriers). But i am not so sure how it actually works on
  2747. Win32/INTEL
  2748. which does not explicitly define any memory visibility rules :(
  2749.  
  2750. >I like your optimization of this. You could improve Alg. 6 as follows:
  2751. >---------- Algorithm 6b ----------
  2752. >signal(bAll) {
  2753. >  _nSig=0
  2754. >  lock counters
  2755. >  // this is safe because nWaiting can only be decremented by a thread
  2756. that
  2757. >  // owns counters and nGone can only be changed by a thread that owns
  2758. >counters.
  2759. >  if (nWaiting>nGone) {
  2760. >    if (0==nSignaled) {
  2761. >      sema_wait gate // close gate if not already closed
  2762. >    }
  2763. >    if (nGone>0) {
  2764. >      nWaiting-=nGone
  2765. >      nGone=0
  2766. >    }
  2767. >    _nSig=bAll?nWaiting:1
  2768. >    nSignaled+=_nSig
  2769. >    nWaiting-=_nSig
  2770. >  }
  2771. >  unlock counters
  2772. >  if (0!=_nSig) {
  2773. >    sema_post queue, _nSig
  2774. >  }
  2775. >}
  2776. >---------- ---------- ----------
  2777. >I guess this wouldn't apply to Alg 8a because nWaitersGone changes
  2778. meanings
  2779. >depending upon whether the gate is open or closed.
  2780.  
  2781. agree.
  2782.  
  2783. >In the loop "while ( nWaitersWasGone-- ) {" you do a sema_wait on
  2784. >semBlockLock. Perhaps waiting on semBlockQueue would be a better idea.
  2785.  
  2786. you are correct. my mistake.
  2787.  
  2788. >What have you gained by making the last thread to be signaled do the waits
  2789. >for all the timed out threads, besides added complexity? It took me a long
  2790. >time to figure out what your objective was with this, to realize you were
  2791. >using nWaitersGone to mean two different things, and to verify that you
  2792. >hadn't introduced any bug by doing this. Even now I'm not 100% sure.
  2793. >
  2794. >What has all this playing about with nWaitersGone really gained us besides
  2795. a
  2796. >lot of complexity (it is much harder to verify that this solution is
  2797. >correct), execution overhead (we now have a lot more if statements to
  2798. >evaluate), and space overhead (more space for the extra code, and another
  2799. >integer in our data)? We did manage to save a lock/unlock pair in an
  2800. >uncommon case (when a time out occurs) at the above mentioned expenses in
  2801. >the common cases.
  2802.  
  2803. well, please consider the following:
  2804.  
  2805. 1) with multiple waiters unblocked (but some timed out) the trick with
  2806. counter
  2807. seem to ensure potentially higher level of concurrency by not delaying
  2808. most of unblocked waiters for semaphore cleanup - only the last one
  2809. will be delayed but all others would already contend/acquire/release
  2810. the external mutex - the critical section protected by mtxUnblockLock is
  2811. made smaller (increment + couple of IFs is faster than system/kernel call)
  2812. which i think is good in general. however, you are right, this is done
  2813. at expense of 'normal' waiters..
  2814.  
  2815. 2) some semaphore APIs (e.g. POSIX IPC sems) do allow to adjust the
  2816. semaphore counter in one call => less system/kernel calls.. imagine:
  2817.  
  2818. if ( 1 == nSignalsWasLeft ) {
  2819.     if ( 0 != nWaitersWasGone ) {
  2820.       ReleaseSemaphore( semBlockQueue,-nWaitersWasGone );  // better now
  2821. than spurious later
  2822.     }
  2823.     sem_post( semBlockLock );              // open the gate
  2824.   }
  2825.  
  2826. 3) even on win32 a single thread doing multiple cleanup calls (to wait)
  2827. will probably result in faster execution (because of processor caching)
  2828. than multiple threads each doing a single call to wait.
  2829.  
  2830. >As for 8b, c, and d, they look ok though I haven't studied them
  2831. thoroughly.
  2832. >What would you use them for?
  2833.  
  2834. 8b) for semaphores which do not allow to unblock multiple waiters
  2835. in a single call to post/release (e.g. POSIX realtime semaphores -
  2836. <semaphore.h>)
  2837.  
  2838. 8c/8d) for WinCE prior to 3.0 (WinCE 3.0 does have semaphores)
  2839.  
  2840. ok. so, which one is the 'final' algorithm(s) which we should use in
  2841. pthreads-win32??
  2842.  
  2843. regards,
  2844. alexander.
  2845.  
  2846. ----------------------------------------------------------------------------
  2847.  
  2848. Louis Thomas <lthomas@arbitrade.com> on 02/27/2001 05:20:12 AM
  2849.  
  2850. Please respond to Louis Thomas <lthomas@arbitrade.com>
  2851.  
  2852. To:   Alexander Terekhov/Germany/IBM@IBMDE
  2853. cc:   rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>, Nanbor Wang
  2854.       <nanbor@cs.wustl.edu>
  2855. Subject:  RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
  2856.       n questions
  2857.  
  2858. Sorry all. Busy week.
  2859.  
  2860. > this insures the fairness
  2861. > which POSIX does not (e.g. two subsequent broadcasts - the gate does
  2862. insure
  2863. > that first wave waiters will start the race for the mutex before waiters
  2864. > from the second wave - Linux pthreads process/unblock both waves
  2865. > concurrently...)
  2866.  
  2867. I'm not sure how we are any more fair about this than Linux. We certainly
  2868. don't guarantee that the threads released by the first broadcast will get
  2869. the external mutex before the threads of the second wave. In fact, it is
  2870. possible that those threads will never get the external mutex if there is
  2871. enough contention for it.
  2872.  
  2873. > e.g. i was thinking about implementation with a pool of
  2874. > N semaphores/counters [...]
  2875.  
  2876. I considered that too. The problem is as you mentioned in a). You really
  2877. need to assign threads to semaphores once you know how you want to wake
  2878. them
  2879. up, not when they first begin waiting which is the only time you can assign
  2880. them.
  2881.  
  2882. > well, i am not quite sure that i've fully understood your scenario,
  2883.  
  2884. Hmm. Well, it think it's an important example, so I'll try again. First, we
  2885. have thread A which we KNOW is waiting on a condition. As soon as it
  2886. becomes
  2887. unblocked for any reason, we will know because it will set a flag. Since
  2888. the
  2889. flag is not set, we are 100% confident that thread A is waiting on the
  2890. condition. We have another thread, thread B, which has acquired the mutex
  2891. and is about to wait on the condition. Thus it is pretty clear that at any
  2892. point, either just A is waiting, or A and B are waiting. Now thread C comes
  2893. along. C is about to do a broadcast on the condition. A broadcast is
  2894. guaranteed to unblock all threads currently waiting on a condition, right?
  2895. Again, we said that either just A is waiting, or A and B are both waiting.
  2896. So, when C does its broadcast, depending upon whether B has started waiting
  2897. or not, thread C will unblock A or unblock A and B. Either way, C must
  2898. unblock A, right?
  2899.  
  2900. Now, you said anything that happens is correct so long as a) "a signal is
  2901. not lost between unlocking the mutex and waiting on the condition" and b)
  2902. "a
  2903. thread must not steal a signal it sent", correct? Requirement b) is easy to
  2904. satisfy: in this scenario, thread C will never wait on the condition, so it
  2905. won't steal any signals.  Requirement a) is not hard either. The only way
  2906. we
  2907. could fail to meet requirement a) in this scenario is if thread B was
  2908. started waiting but didn't wake up because a signal was lost. This will not
  2909. happen.
  2910.  
  2911. Now, here is what happens. Assume thread C beats thread B. Thread C looks
  2912. to
  2913. see how many threads are waiting on the condition. Thread C sees just one
  2914. thread, thread A, waiting. It does a broadcast waking up just one thread
  2915. because just one thread is waiting. Next, before A can become unblocked,
  2916. thread B begins waiting. Now there are two threads waiting, but only one
  2917. will be unblocked. Suppose B wins. B will become unblocked. A will not
  2918. become unblocked, because C only unblocked one thread (sema_post cond, 1).
  2919. So at the end, B finishes and A remains blocked.
  2920.  
  2921. We have met both of your requirements, so by your rules, this is an
  2922. acceptable outcome. However, I think that the spec says this is an
  2923. unacceptable outcome! We know for certain that A was waiting and that C did
  2924. a broadcast, but A did not become unblocked! Yet, the spec says that a
  2925. broadcast wakes up all waiting threads. This did not happen. Do you agree
  2926. that this shows your rules are not strict enough?
  2927.  
  2928. > and what about N2? :) this one does allow almost everything.
  2929.  
  2930. Don't get me started about rule #2. I'll NEVER advocate an algorithm that
  2931. uses rule 2 as an excuse to suck!
  2932.  
  2933. > but it is done (decrement)under mutex protection - this is not a subject
  2934. > of a race condition.
  2935.  
  2936. You are correct. My mistake.
  2937.  
  2938. > i would remove "_bTimedOut=false".. after all, it was a real timeout..
  2939.  
  2940. I disagree. A thread that can't successfully retract its waiter status
  2941. can't
  2942. really have timed out. If a thread can't return without executing extra
  2943. code
  2944. to deal with the fact that someone tried to unblock it, I think it is a
  2945. poor
  2946. idea to pretend we
  2947. didn't realize someone was trying to signal us. After all, a signal is more
  2948. important than a time out.
  2949.  
  2950. > when nSignaled != 0, it is possible to update nWaiters (--) and do not
  2951. > touch nGone
  2952.  
  2953. I realize this, but I was thinking that writing it the other ways saves
  2954. another if statement.
  2955.  
  2956. > adjust only if nGone != 0 and save one cache memory write - probably much
  2957. slower than 'if'
  2958.  
  2959. Hmm. You are probably right.
  2960.  
  2961. > well, in a strange (e.g. timeout test) program you may (theoretically)
  2962. > have an overflow of nWaiters/nGone counters (with waiters repeatedly
  2963. timing
  2964. > out and no signals at all).
  2965.  
  2966. Also true. Not only that, but you also have the possibility that one could
  2967. overflow the number of waiters as well! However, considering the limit you
  2968. have chosen for nWaitersGone, I suppose it is unlikely that anyone would be
  2969. able to get INT_MAX/2 threads waiting on a single condition. :)
  2970.  
  2971. Analysis of 8a:
  2972.  
  2973. It looks correct to me.
  2974.  
  2975. What are IPC semaphores?
  2976.  
  2977. In the line where you state, "else if ( nWaitersBlocked > nWaitersGone ) {
  2978. // HARMLESS RACE CONDITION!" there is no race condition for nWaitersGone
  2979. because nWaitersGone is never modified without holding mtxUnblockLock. You
  2980. are correct that there is a harmless race on nWaitersBlocked, which can
  2981. increase and make the condition become true just after we check it. If this
  2982. happens, we interpret it as the wait starting after the signal.
  2983.  
  2984. I like your optimization of this. You could improve Alg. 6 as follows:
  2985. ---------- Algorithm 6b ----------
  2986. signal(bAll) {
  2987.   _nSig=0
  2988.   lock counters
  2989.   // this is safe because nWaiting can only be decremented by a thread that
  2990.   // owns counters and nGone can only be changed by a thread that owns
  2991. counters.
  2992.   if (nWaiting>nGone) {
  2993.     if (0==nSignaled) {
  2994.       sema_wait gate // close gate if not already closed
  2995.     }
  2996.     if (nGone>0) {
  2997.       nWaiting-=nGone
  2998.       nGone=0
  2999.     }
  3000.     _nSig=bAll?nWaiting:1
  3001.     nSignaled+=_nSig
  3002.     nWaiting-=_nSig
  3003.   }
  3004.   unlock counters
  3005.   if (0!=_nSig) {
  3006.     sema_post queue, _nSig
  3007.   }
  3008. }
  3009. ---------- ---------- ----------
  3010. I guess this wouldn't apply to Alg 8a because nWaitersGone changes meanings
  3011. depending upon whether the gate is open or closed.
  3012.  
  3013. In the loop "while ( nWaitersWasGone-- ) {" you do a sema_wait on
  3014. semBlockLock. Perhaps waiting on semBlockQueue would be a better idea.
  3015.  
  3016. What have you gained by making the last thread to be signaled do the waits
  3017. for all the timed out threads, besides added complexity? It took me a long
  3018. time to figure out what your objective was with this, to realize you were
  3019. using nWaitersGone to mean two different things, and to verify that you
  3020. hadn't introduced any bug by doing this. Even now I'm not 100% sure.
  3021.  
  3022. What has all this playing about with nWaitersGone really gained us besides
  3023. a
  3024. lot of complexity (it is much harder to verify that this solution is
  3025. correct), execution overhead (we now have a lot more if statements to
  3026. evaluate), and space overhead (more space for the extra code, and another
  3027. integer in our data)? We did manage to save a lock/unlock pair in an
  3028. uncommon case (when a time out occurs) at the above mentioned expenses in
  3029. the common cases.
  3030.  
  3031. As for 8b, c, and d, they look ok though I haven't studied them thoroughly.
  3032. What would you use them for?
  3033.  
  3034.     Later,
  3035.         -Louis! :)
  3036.  
  3037.