README
1PTHREADS-WIN32
2==============
3
4Pthreads-win32 is free software, distributed under the GNU Lesser
5General Public License (LGPL). See the file 'COPYING.LIB' for terms
6and conditions. Also see the file 'COPYING' for information
7specific to pthreads-win32, copyrights and the LGPL.
8
9
10What is it?
11-----------
12
13Pthreads-win32 is an Open Source Software implementation of the
14Threads component of the POSIX 1003.1c 1995 Standard (or later)
15for Microsoft's Win32 environment. Some functions from POSIX
161003.1b are also supported including semaphores. Other related
17functions include the set of read-write lock functions. The
18library also supports some of the functionality of the Open
19Group's Single Unix specification, version 2, namely mutex types,
20plus some common and pthreads-win32 specific non-portable
21routines (see README.NONPORTABLE).
22
23See the file "ANNOUNCE" for more information including standards
24conformance details and the list of supported and unsupported
25routines.
26
27
28Prerequisites
29-------------
30MSVC or GNU C (MinGW32 MSys development kit)
31 To build from source.
32
33QueueUserAPCEx by Panagiotis E. Hadjidoukas
34 To support any thread cancelation in C++ library builds or
35 to support cancelation of blocked threads in any build.
36 This library is not required otherwise.
37
38 For true async cancelation of threads (including blocked threads).
39 This is a DLL and Windows driver that provides pre-emptive APC
40 by forcing threads into an alertable state when the APC is queued.
41 Both the DLL and driver are provided with the pthreads-win32.exe
42 self-unpacking ZIP, and on the pthreads-win32 FTP site (in source
43 and pre-built forms). Currently this is a separate LGPL package to
44 pthreads-win32. See the README in the QueueUserAPCEx folder for
45 installation instructions.
46
47 Pthreads-win32 will automatically detect if the QueueUserAPCEx DLL
48 QuserEx.DLL is available and whether the driver AlertDrv.sys is
49 loaded. If it is not available, pthreads-win32 will simulate async
50 cancelation, which means that it can async cancel only threads that
51 are runnable. The simulated async cancellation cannot cancel blocked
52 threads.
53
54 [FOR SECURITY] To be found Quserex.dll MUST be installed in the
55 Windows System Folder. This is not an unreasonable constraint given a
56 driver must also be installed and loaded at system startup.
57
58
59Library naming
60--------------
61
62Because the library is being built using various exception
63handling schemes and compilers - and because the library
64may not work reliably if these are mixed in an application,
65each different version of the library has it's own name.
66
67Note 1: the incompatibility is really between EH implementations
68of the different compilers. It should be possible to use the
69standard C version from either compiler with C++ applications
70built with a different compiler. If you use an EH version of
71the library, then you must use the same compiler for the
72application. This is another complication and dependency that
73can be avoided by using only the standard C library version.
74
75Note 2: if you use a standard C pthread*.dll with a C++
76application, then any functions that you define that are
77intended to be called via pthread_cleanup_push() must be
78__cdecl.
79
80Note 3: the intention was to also name either the VC or GC
81version (it should be arbitrary) as pthread.dll, including
82pthread.lib and libpthread.a as appropriate. This is no longer
83likely to happen.
84
85Note 4: the compatibility number was added so that applications
86can differentiate between binary incompatible versions of the
87libs and dlls.
88
89In general:
90 pthread[VG]{SE,CE,C}[c].dll
91 pthread[VG]{SE,CE,C}[c].lib
92
93where:
94 [VG] indicates the compiler
95 V - MS VC, or
96 G - GNU C
97
98 {SE,CE,C} indicates the exception handling scheme
99 SE - Structured EH, or
100 CE - C++ EH, or
101 C - no exceptions - uses setjmp/longjmp
102
103 c - DLL compatibility number indicating ABI and API
104 compatibility with applications built against
105 a snapshot with the same compatibility number.
106 See 'Version numbering' below.
107
108The name may also be suffixed by a 'd' to indicate a debugging version
109of the library. E.g. pthreadVC2d.lib. Debugging versions contain
110additional information for debugging (symbols etc) and are often not
111optimised in any way (compiled with optimisation turned off).
112
113Examples:
114 pthreadVSE.dll (MSVC/SEH)
115 pthreadGCE.dll (GNUC/C++ EH)
116 pthreadGC.dll (GNUC/not dependent on exceptions)
117 pthreadVC1.dll (MSVC/not dependent on exceptions - not binary
118 compatible with pthreadVC.dll)
119 pthreadVC2.dll (MSVC/not dependent on exceptions - not binary
120 compatible with pthreadVC1.dll or pthreadVC.dll)
121
122The GNU library archive file names have correspondingly changed to:
123
124 libpthreadGCEc.a
125 libpthreadGCc.a
126
127
128Versioning numbering
129--------------------
130
131Version numbering is separate from the snapshot dating system, and
132is the canonical version identification system embedded within the
133DLL using the Microsoft version resource system. The versioning
134system chosen follows the GNU Libtool system. See
135http://www.gnu.org/software/libtool/manual.html section 6.2.
136
137See the resource file 'version.rc'.
138
139Microsoft version numbers use 4 integers:
140
141 0.0.0.0
142
143Pthreads-win32 uses the first 3 following the Libtool convention.
144The fourth is commonly used for the build number, but will be reserved
145for future use.
146
147 current.revision.age.0
148
149The numbers are changed as follows:
150
1511. If the library source code has changed at all since the last update,
152 then increment revision (`c:r:a' becomes `c:r+1:a').
1532. If any interfaces have been added, removed, or changed since the last
154 update, increment current, and set revision to 0.
1553. If any interfaces have been added since the last public release, then
156 increment age.
1574. If any interfaces have been removed or changed since the last public
158 release, then set age to 0.
159
160
161DLL compatibility numbering is an attempt to ensure that applications
162always load a compatible pthreads-win32 DLL by using a DLL naming system
163that is consistent with the version numbering system. It also allows
164older and newer DLLs to coexist in the same filesystem so that older
165applications can continue to be used. For pre .NET Windows systems,
166this inevitably requires incompatible versions of the same DLLs to have
167different names.
168
169Pthreads-win32 has adopted the Cygwin convention of appending a single
170integer number to the DLL name. The number used is based on the library
171version number and is computed as 'current' - 'age'.
172
173(See http://home.att.net/~perlspinr/libversioning.html for a nicely
174detailed explanation.)
175
176Using this method, DLL name/s will only change when the DLL's
177backwards compatibility changes. Note that the addition of new
178'interfaces' will not of itself change the DLL's compatibility for older
179applications.
180
181
182Which of the several dll versions to use?
183-----------------------------------------
184or,
185---
186What are all these pthread*.dll and pthread*.lib files?
187-------------------------------------------------------
188
189Simple, use either pthreadGCv.* if you use GCC, or pthreadVCv.* if you
190use MSVC - where 'v' is the DLL versioning (compatibility) number.
191
192Otherwise, you need to choose carefully and know WHY.
193
194The most important choice you need to make is whether to use a
195version that uses exceptions internally, or not. There are versions
196of the library that use exceptions as part of the thread
197cancelation and exit implementation. The default version uses
198setjmp/longjmp.
199
200There is some contension amongst POSIX threads experts as
201to how POSIX threads cancelation and exit should work
202with languages that use exceptions, e.g. C++ and even C
203(Microsoft's Structured Exceptions).
204
205The issue is: should cancelation of a thread in, say,
206a C++ application cause object destructors and C++ exception
207handlers to be invoked as the stack unwinds during thread
208exit, or not?
209
210There seems to be more opinion in favour of using the
211standard C version of the library (no EH) with C++ applications
212for the reason that this appears to be the assumption commercial
213pthreads implementations make. Therefore, if you use an EH version
214of pthreads-win32 then you may be under the illusion that
215your application will be portable, when in fact it is likely to
216behave differently when linked with other pthreads libraries.
217
218Now you may be asking: then why have you kept the EH versions of
219the library?
220
221There are a couple of reasons:
222- there is division amongst the experts and so the code may
223 be needed in the future. Yes, it's in the repository and we
224 can get it out anytime in the future, but it would be difficult
225 to find.
226- pthreads-win32 is one of the few implementations, and possibly
227 the only freely available one, that has EH versions. It may be
228 useful to people who want to play with or study application
229 behaviour under these conditions.
230
231Notes:
232
233[If you use either pthreadVCE or pthreadGCE]
234
2351. [See also the discussion in the FAQ file - Q2, Q4, and Q5]
236
237If your application contains catch(...) blocks in your POSIX
238threads then you will need to replace the "catch(...)" with the macro
239"PtW32Catch", eg.
240
241 #ifdef PtW32Catch
242 PtW32Catch {
243 ...
244 }
245 #else
246 catch(...) {
247 ...
248 }
249 #endif
250
251Otherwise neither pthreads cancelation nor pthread_exit() will work
252reliably when using versions of the library that use C++ exceptions
253for cancelation and thread exit.
254
255This is due to what is believed to be a C++ compliance error in VC++
256whereby you may not have multiple handlers for the same exception in
257the same try/catch block. GNU G++ doesn't have this restriction.
258
259
260Other name changes
261------------------
262
263All snapshots prior to and including snapshot 2000-08-13
264used "_pthread_" as the prefix to library internal
265functions, and "_PTHREAD_" to many library internal
266macros. These have now been changed to "ptw32_" and "PTW32_"
267respectively so as to not conflict with the ANSI standard's
268reservation of identifiers beginning with "_" and "__" for
269use by compiler implementations only.
270
271If you have written any applications and you are linking
272statically with the pthreads-win32 library then you may have
273included a call to _pthread_processInitialize. You will
274now have to change that to ptw32_processInitialize.
275
276
277Cleanup code default style
278--------------------------
279
280Previously, if not defined, the cleanup style was determined automatically
281from the compiler used, and one of the following was defined accordingly:
282
283 __CLEANUP_SEH MSVC only
284 __CLEANUP_CXX C++, including MSVC++, GNU G++
285 __CLEANUP_C C, including GNU GCC, not MSVC
286
287These defines determine the style of cleanup (see pthread.h) and,
288most importantly, the way that cancelation and thread exit (via
289pthread_exit) is performed (see the routine ptw32_throw()).
290
291In short, the exceptions versions of the library throw an exception
292when a thread is canceled, or exits via pthread_exit(). This exception is
293caught by a handler in the thread startup routine, so that the
294the correct stack unwinding occurs regardless of where the thread
295is when it's canceled or exits via pthread_exit().
296
297In this snapshot, unless the build explicitly defines (e.g. via a
298compiler option) __CLEANUP_SEH, __CLEANUP_CXX, or __CLEANUP_C, then
299the build NOW always defaults to __CLEANUP_C style cleanup. This style
300uses setjmp/longjmp in the cancelation and pthread_exit implementations,
301and therefore won't do stack unwinding even when linked to applications
302that have it (e.g. C++ apps). This is for consistency with most/all
303commercial Unix POSIX threads implementations.
304
305Although it was not clearly documented before, it is still necessary to
306build your application using the same __CLEANUP_* define as was
307used for the version of the library that you link with, so that the
308correct parts of pthread.h are included. That is, the possible
309defines require the following library versions:
310
311 __CLEANUP_SEH pthreadVSE.dll
312 __CLEANUP_CXX pthreadVCE.dll or pthreadGCE.dll
313 __CLEANUP_C pthreadVC.dll or pthreadGC.dll
314
315It is recommended that you let pthread.h use it's default __CLEANUP_C
316for both library and application builds. That is, don't define any of
317the above, and then link with pthreadVC.lib (MSVC or MSVC++) and
318libpthreadGC.a (MinGW GCC or G++). The reason is explained below, but
319another reason is that the prebuilt pthreadVCE.dll is currently broken.
320Versions built with MSVC++ later than version 6 may not be broken, but I
321can't verify this yet.
322
323WHY ARE WE MAKING THE DEFAULT STYLE LESS EXCEPTION-FRIENDLY?
324Because no commercial Unix POSIX threads implementation allows you to
325choose to have stack unwinding. Therefore, providing it in pthread-win32
326as a default is dangerous. We still provide the choice but unless
327you consciously choose to do otherwise, your pthreads applications will
328now run or crash in similar ways irrespective of the pthreads platform
329you use. Or at least this is the hope.
330
331
332Building under VC++ using C++ EH, Structured EH, or just C
333----------------------------------------------------------
334
335From the source directory run nmake without any arguments to list
336help information. E.g.
337
338$ nmake
339
340Microsoft (R) Program Maintenance Utility Version 6.00.8168.0
341Copyright (C) Microsoft Corp 1988-1998. All rights reserved.
342
343Run one of the following command lines:
344nmake clean VCE (to build the MSVC dll with C++ exception handling)
345nmake clean VSE (to build the MSVC dll with structured exception handling)
346nmake clean VC (to build the MSVC dll with C cleanup code)
347nmake clean VCE-inlined (to build the MSVC inlined dll with C++ exception handling)
348nmake clean VSE-inlined (to build the MSVC inlined dll with structured exception handling)
349nmake clean VC-inlined (to build the MSVC inlined dll with C cleanup code)
350nmake clean VC-static (to build the MSVC static lib with C cleanup code)
351nmake clean VCE-debug (to build the debug MSVC dll with C++ exception handling)
352nmake clean VSE-debug (to build the debug MSVC dll with structured exception handling)
353nmake clean VC-debug (to build the debug MSVC dll with C cleanup code)
354nmake clean VCE-inlined-debug (to build the debug MSVC inlined dll with C++ exception handling)
355nmake clean VSE-inlined-debug (to build the debug MSVC inlined dll with structured exception handling)
356nmake clean VC-inlined-debug (to build the debug MSVC inlined dll with C cleanup code)
357nmake clean VC-static-debug (to build the debug MSVC static lib with C cleanup code)
358
359
360The pre-built dlls are normally built using the *-inlined targets.
361
362You can run the testsuite by changing to the "tests" directory and
363running nmake. E.g.:
364
365$ cd tests
366$ nmake
367
368Microsoft (R) Program Maintenance Utility Version 6.00.8168.0
369Copyright (C) Microsoft Corp 1988-1998. All rights reserved.
370
371Run one of the following command lines:
372nmake clean VC (to test using VC dll with VC (no EH) applications)
373nmake clean VCX (to test using VC dll with VC++ (EH) applications)
374nmake clean VCE (to test using the VCE dll with VC++ EH applications)
375nmake clean VSE (to test using VSE dll with VC (SEH) applications)
376nmake clean VC-bench (to benchtest using VC dll with C bench app)
377nmake clean VCX-bench (to benchtest using VC dll with C++ bench app)
378nmake clean VCE-bench (to benchtest using VCE dll with C++ bench app)
379nmake clean VSE-bench (to benchtest using VSE dll with SEH bench app)
380nmake clean VC-static (to test using VC static lib with VC (no EH) applications)
381
382
383Building under Mingw32
384----------------------
385
386The dll can be built easily with recent versions of Mingw32.
387(The distributed versions are built using Mingw32 and MsysDTK
388from www.mingw32.org.)
389
390From the source directory, run make for help information. E.g.:
391
392$ make
393Run one of the following command lines:
394make clean GC (to build the GNU C dll with C cleanup code)
395make clean GCE (to build the GNU C dll with C++ exception handling)
396make clean GC-inlined (to build the GNU C inlined dll with C cleanup code)
397make clean GCE-inlined (to build the GNU C inlined dll with C++ exception handling)
398make clean GC-static (to build the GNU C inlined static lib with C cleanup code)
399make clean GC-debug (to build the GNU C debug dll with C cleanup code)
400make clean GCE-debug (to build the GNU C debug dll with C++ exception handling)
401make clean GC-inlined-debug (to build the GNU C inlined debug dll with C cleanup code)
402make clean GCE-inlined-debug (to build the GNU C inlined debug dll with C++ exception handling)
403make clean GC-static-debug (to build the GNU C inlined static debug lib with C cleanup code)
404
405
406The pre-built dlls are normally built using the *-inlined targets.
407
408You can run the testsuite by changing to the "tests" directory and
409running make for help information. E.g.:
410
411$ cd tests
412$ make
413Run one of the following command lines:
414make clean GC (to test using GC dll with C (no EH) applications)
415make clean GCX (to test using GC dll with C++ (EH) applications)
416make clean GCE (to test using GCE dll with C++ (EH) applications)
417make clean GC-bench (to benchtest using GNU C dll with C cleanup code)
418make clean GCE-bench (to benchtest using GNU C dll with C++ exception handling)
419make clean GC-static (to test using GC static lib with C (no EH) applications)
420
421
422Building under Linux using the Mingw32 cross development tools
423--------------------------------------------------------------
424
425You can build the library without leaving Linux by using the Mingw32 cross
426development toolchain. See http://www.libsdl.org/extras/win32/cross/ for
427tools and info. The GNUmakefile contains some support for this, for example:
428
429make CROSS=i386-mingw32msvc- clean GC-inlined
430
431will build pthreadGCn.dll and libpthreadGCn.a (n=version#), provided your
432cross-tools/bin directory is in your PATH (or use the cross-make.sh script
433at the URL above).
434
435
436Building the library as a statically linkable library
437-----------------------------------------------------
438
439General: PTW32_STATIC_LIB must be defined for both the library build and the
440application build. The makefiles supplied and used by the following 'make'
441command lines will define this for you.
442
443MSVC (creates pthreadVCn.lib as a static link lib):
444
445nmake clean VC-static
446
447
448MinGW32 (creates libpthreadGCn.a as a static link lib):
449
450make clean GC-static
451
452
453Define PTW32_STATIC_LIB when building your application. Also, your
454application must call a two non-portable routines to initialise the
455some state on startup and cleanup before exit. One other routine needs
456to be called to cleanup after any Win32 threads have called POSIX API
457routines. See README.NONPORTABLE or the html reference manual pages for
458details on these routines:
459
460BOOL pthread_win32_process_attach_np (void);
461BOOL pthread_win32_process_detach_np (void);
462BOOL pthread_win32_thread_attach_np (void); // Currently a no-op
463BOOL pthread_win32_thread_detach_np (void);
464
465
466The tests makefiles have the same targets but only check that the
467static library is statically linkable. They don't run the full
468testsuite. To run the full testsuite, build the dlls and run the
469dll test targets.
470
471
472Building the library under Cygwin
473---------------------------------
474
475Cygwin is implementing it's own POSIX threads routines and these
476will be the ones to use if you develop using Cygwin.
477
478
479Ready to run binaries
480---------------------
481
482For convenience, the following ready-to-run files can be downloaded
483from the FTP site (see under "Availability" below):
484
485 pthread.h
486 semaphore.h
487 sched.h
488 pthreadVC.dll - built with MSVC compiler using C setjmp/longjmp
489 pthreadVC.lib
490 pthreadVCE.dll - built with MSVC++ compiler using C++ EH
491 pthreadVCE.lib
492 pthreadVSE.dll - built with MSVC compiler using SEH
493 pthreadVSE.lib
494 pthreadGC.dll - built with Mingw32 GCC
495 libpthreadGC.a - derived from pthreadGC.dll
496 pthreadGCE.dll - built with Mingw32 G++
497 libpthreadGCE.a - derived from pthreadGCE.dll
498
499As of August 2003 pthreads-win32 pthreadG* versions are built and tested
500using the MinGW + MsysDTK environment current as of that date or later.
501The following file MAY be needed for older MinGW environments.
502
503 gcc.dll - needed to build and run applications that use
504 pthreadGCE.dll.
505
506
507Building applications with GNU compilers
508----------------------------------------
509
510If you're using pthreadGC.dll:
511
512With the three header files, pthreadGC.dll and libpthreadGC.a in the
513same directory as your application myapp.c, you could compile, link
514and run myapp.c under Mingw32 as follows:
515
516 gcc -o myapp.exe myapp.c -I. -L. -lpthreadGC
517 myapp
518
519Or put pthreadGC.dll in an appropriate directory in your PATH,
520put libpthreadGC.a in your system lib directory, and
521put the three header files in your system include directory,
522then use:
523
524 gcc -o myapp.exe myapp.c -lpthreadGC
525 myapp
526
527
528If you're using pthreadGCE.dll:
529
530With the three header files, pthreadGCE.dll, gcc.dll and libpthreadGCE.a
531in the same directory as your application myapp.c, you could compile,
532link and run myapp.c under Mingw32 as follows:
533
534 gcc -x c++ -o myapp.exe myapp.c -I. -L. -lpthreadGCE
535 myapp
536
537Or put pthreadGCE.dll and gcc.dll in an appropriate directory in
538your PATH, put libpthreadGCE.a in your system lib directory, and
539put the three header files in your system include directory,
540then use:
541
542 gcc -x c++ -o myapp.exe myapp.c -lpthreadGCE
543 myapp
544
545
546Availability
547------------
548
549The complete source code in either unbundled, self-extracting
550Zip file, or tar/gzipped format can be found at:
551
552 ftp://sources.redhat.com/pub/pthreads-win32
553
554The pre-built DLL, export libraries and matching pthread.h can
555be found at:
556
557 ftp://sources.redhat.com/pub/pthreads-win32/dll-latest
558
559Home page:
560
561 http://sources.redhat.com/pthreads-win32/
562
563
564Mailing list
565------------
566
567There is a mailing list for discussing pthreads on Win32.
568To join, send email to:
569
570 pthreads-win32-subscribe@sources.redhat.com
571
572Unsubscribe by sending mail to:
573
574 pthreads-win32-unsubscribe@sources.redhat.com
575
576
577Acknowledgements
578----------------
579
580See the ANNOUNCE file for acknowledgements.
581See the 'CONTRIBUTORS' file for the list of contributors.
582
583As much as possible, the ChangeLog file attributes
584contributions and patches that have been incorporated
585in the library to the individuals responsible.
586
587Finally, thanks to all those who work on and contribute to the
588POSIX and Single Unix Specification standards. The maturity of an
589industry can be measured by it's open standards.
590
591----
592Ross Johnson
593<rpj@callisto.canberra.edu.au>
594
595
596
597
598
599
600
601
602
README.Borland
1In ptw32_InterlockedCompareExchange.c, I've added a section for
2Borland's compiler; it's identical to that for the MS compiler except
3that it uses /* ... */ comments instead of ; comments.
4
5[RPJ: need to define HAVE_TASM32 in config.h to use the above.]
6
7
8The other file is a makefile suitable for use with Borland's compiler
9(run "make -fBmakefile" in the directory). It builds a single version
10of the library, pthreadBC.dll and the corresponding pthreadBC.lib
11import library, which is comparable to the pthreadVC version; I can't
12personally see any demand for the versions that include structured or
13C++ exception cancellation handling so I haven't attempted to build
14those versions of the library. (I imagine a static version might be
15of use to some, but we can't legally use that on my commercial
16projects so I can't try that out, unfortunately.)
17
18[RPJ: Added tests\Bmakefile as well.]
19
20Borland C++ doesn't define the ENOSYS constant used by pthreads-win32;
21rather than make more extensive patches to the pthreads-win32 source I
22have a mostly-arbitrary constant for it in the makefile. However this
23doesn't make it visible to the application using the library, so if
24anyone actually wants to use this constant in their apps (why?)
25someone might like to make a seperate NEED_BCC_something define to add
26this stuff.
27
28The makefile also #defines EDEADLK as EDEADLOCK, _timeb as timeb, and
29_ftime as ftime, to deal with the minor differences between the two
30RTLs' naming conventions, and sets the compiler flags as required to
31get a normal compile of the library.
32
33[RPJ: Moved errno values and _timeb etc to pthread.h, so apps will also
34use them.]
35
36(While I'm on the subject, the reason Borland users should recompile
37the library, rather than using the impdef/implib technique suggested
38previously on the mailing list, is that a) the errno constants are
39different, so the results returned by the pthread_* functions can be
40meaningless, and b) the errno variable/pseudo-variable itself is
41different in the MS & BCC runtimes, so you can't access the
42pthreadVC's errno from a Borland C++-compiled host application
43correctly - I imagine there are other potential problems from the RTL
44mismatch too.)
45
46[RPJ: Make sure you use the same RTL in both dll and application builds.
47The dll and tests Bmakefiles use cw32mti.lib. Having some trouble with
48memory read exceptions running the test suite using BCC55.]
49
50Best regards,
51Will
52
53--
54Will Bryant
55Systems Architect, eCOSM Limited
56Cell +64 21 655 443, office +64 3 365 4176
57http://www.ecosm.com/
58
README.CV
1README.CV -- Condition Variables
2--------------------------------
3
4The original implementation of condition variables in
5pthreads-win32 was based on a discussion paper:
6
7"Strategies for Implementing POSIX Condition Variables
8on Win32": http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
9
10The changes suggested below were made on Feb 6 2001. This
11file is included in the package for the benefit of anyone
12interested in understanding the pthreads-win32 implementation
13of condition variables and the (sometimes subtle) issues that
14it attempts to resolve.
15
16Thanks go to the individuals whose names appear throughout
17the following text.
18
19Ross Johnson
20
21--------------------------------------------------------------------
22
23fyi.. (more detailed problem description/demos + possible fix/patch)
24
25regards,
26alexander.
27
28
29Alexander Terekhov
3031.01.2001 17:43
31
32To: ace-bugs@cs.wustl.edu
33cc:
34From: Alexander Terekhov/Germany/IBM@IBMDE
35Subject: 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.
147David 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
163The implementation you suggested is indeed correct
164one (yes, now I see it :). However it requires from
165signal/broadcast nothing more than to "{ return 0; }"
166That is not the case for pthread-win32 and ACE
167implementations. I do think that these implementations
168(basically the same implementation) have some serious
169problems with wait/signal/broadcast calls. I am looking
170for help to clarify whether these problems are real
171or not. I think that I can demonstrate what I mean
172using one or two small sample programs.
173.
174.
175.
176==========
177tennis.cpp
178==========
179
180#include "ace/Synch.h"
181#include "ace/Thread.h"
182
183enum 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
194enum GAME_STATE eGameState;
195ACE_Mutex* pmtxGameStateLock;
196ACE_Condition< ACE_Mutex >* pcndGameStateChange;
197
198void*
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
245void*
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
293int
294main (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===========
336tennisb.cpp
337===========
338#include "ace/Synch.h"
339#include "ace/Thread.h"
340
341enum 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
352enum GAME_STATE eGameState;
353ACE_Mutex* pmtxGameStateLock;
354ACE_Condition< ACE_Mutex >* pcndGameStateChange;
355
356void*
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
403void*
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
451int
452main (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.
504David 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
529well, you can probably loose your bet.. it was indeed stolen
530by "another" thread but not the one you seem to think of.
531
532I think that what actually happens is the following:
533
534H:\SA\UXX\pt\PTHREADS\TESTS>tennis3.exe
535
536PLAYER-A
537
538PLAYER-B
539
540----PLAYER-B: SPURIOUS WAKEUP!!!
541
542PLAYER-A GONE
543
544PLAYER-B GONE
545
546GAME OVER
547
548H:\SA\UXX\pt\PTHREADS\TESTS>
549
550here you can see that PLAYER-B after playing his first
551ball (which came via signal from PLAYER-A) just dropped
552it down. What happened is that his signal to player A
553was consumed as spurious wakeup by himself (player B).
554
555The implementation has a problem:
556
557================
558waiting 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
578Player-A after playing game's initial ball went into
579wait (called _wait) but was pre-empted before reaching
580wait semaphore. He was counted as waiter but was not
581actually waiting/blocked yet.
582
583===============
584signal 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
599Player-B after he received signal/ball from Player A
600called _signal. The _signal did see that there was
601one waiter blocked on the condition (Player-A) and
602released the semaphore.. (but it did not unblock
603Player-A because he was not actually blocked).
604Player-B thread continued its execution, called _wait,
605was counted as second waiter BUT was allowed to slip
606through opened semaphore gate (which was opened for
607Player-B) and received his own signal. Player B remained
608blocked followed by Player A. Deadlock happened which
609lasted until main thread came in and said game over.
610
611It seems to me that the implementation fails to
612correctly implement the following statement
613from specification:
614
615http://www.opengroup.org/
616onlinepubs/007908799/xsh/pthread_cond_wait.html
617
618"These functions atomically release mutex and cause
619the calling thread to block on the condition variable
620cond; atomically here means "atomically with respect
621to access by another thread to the mutex and then the
622condition variable". That is, if another thread is
623able to acquire the mutex after the about-to-block
624thread has released it, then a subsequent call to
625pthread_cond_signal() or pthread_cond_broadcast()
626in that thread behaves as if it were issued after
627the about-to-block thread has blocked."
628
629Question: Am I right?
630
631(I produced the program output above by simply
632adding ?Sleep( 1 )?:
633
634================
635waiting 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
649Sleep( 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
657to the source code of pthread-win32 implementation:
658
659http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/
660condvar.c?rev=1.36&content-type=text/
661x-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
680Sleep( 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
704BTW, on my system (2 CPUs) I can manage to get
705signals lost even without any source code modification
706if I run the tennis program many times in different
707shell sessions.
708.
709.
710.
711David 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
723Okay, lets change 'pthread_cond_signal' to 'pthread_cond_broadcast'
724but also add some noise from main() right before declaring the game
725to be over (I need it in order to demonstrate another problem of
726pthread-win32/ACE implementations - broadcast deadlock)...
727.
728.
729.
730It is my understanding of POSIX conditions,
731that on correct implementation added noise
732in form of unnecessary broadcasts from main,
733should not break the tennis program. The
734only 'side effect' of added noise on correct
735implementation would be 'spurious wakeups' of
736players (in fact they are not spurious,
737players just see them as spurious) unblocked,
738not by another player but by main before
739another player had a chance to acquire the
740mutex and change the game state variable:
741.
742.
743.
744
745PLAYER-B
746
747PLAYER-A
748
749---Noise ON...
750
751PLAYER-B
752
753PLAYER-A
754
755.
756.
757.
758
759PLAYER-B
760
761PLAYER-A
762
763----PLAYER-A: SPURIOUS WAKEUP!!!
764
765PLAYER-B
766
767PLAYER-A
768
769---Noise OFF
770
771PLAYER-B
772
773---Stopping the game...
774
775PLAYER-A GONE
776
777PLAYER-B GONE
778
779GAME OVER
780
781H:\SA\UXX\pt\PTHREADS\TESTS>
782
783On pthread-win32/ACE implementations the
784program could stall:
785
786.
787.
788.
789
790PLAYER-A
791
792PLAYER-B
793
794PLAYER-A
795
796PLAYER-B
797
798PLAYER-A
799
800PLAYER-B
801
802PLAYER-A
803
804PLAYER-B
805
806---Noise ON...
807
808PLAYER-A
809
810---Noise OFF
811^C
812H:\SA\UXX\pt\PTHREADS\TESTS>
813
814
815The implementation has problems:
816
817================
818waiting 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==================
875broadcast 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
890if ( 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
902endif
903
904a) cond.waiters_count does not accurately reflect
905number of waiters blocked on semaphore - that could
906result (in the time window when counter is not accurate)
907in spurios wakeups organised by subsequent _signals
908and _broadcasts. From standard compliance point of view
909that is OK but that could be a real problem from
910performance/efficiency point of view.
911
912b) If subsequent broadcast happen to go into wait on
913cond.auto_reset_event_or_sem before previous
914broadcast was unblocked from cond.auto_reset_event_or_sem
915by its last waiter, one of two blocked threads will
916remain blocked because last_waiter processing code
917fails to unblock both threads.
918
919In the situation with tennisb.c the Player-B was put
920in a deadlock by noise (broadcast) coming from main
921thread. And since Player-B holds the game state
922mutex when it calls broadcast, the whole program
923stalled: Player-A was deadlocked on mutex and
924main thread after finishing with producing the noise
925was deadlocked on mutex too (needed to declare the
926game over)
927
928(I produced the program output above by simply
929adding ?Sleep( 1 )?:
930
931==================
932broadcast 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
947if ( waiters_count != 0 )
948
949Sleep( 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
961endif
962
963to the source code of pthread-win32 implementation:
964
965http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/
966condvar.c?rev=1.36&content-type=text/
967x-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
975Sleep( 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
1018BTW, on my system (2 CPUs) I can manage to get
1019the program stalled even without any source code
1020modification if I run the tennisb program many
1021times in different shell sessions.
1022
1023===================
1024pthread-win32 patch
1025===================
1026struct 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
1050cancellation*/
1051
1052int
1053pthread_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 */
1111FAIL2:
1112 (void) sem_destroy (&(cv->semBlockQueue));
1113
1114FAIL1:
1115 (void) sem_destroy (&(cv->semBlockLock));
1116
1117FAIL0:
1118DONE:
1119 *cond = cv;
1120
1121 return (result);
1122
1123} /* pthread_cond_init */
1124
1125int
1126pthread_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 */
1221typedef struct {
1222 pthread_mutex_t * mutexPtr;
1223 pthread_cond_t cv;
1224 int * resultPtr;
1225} ptw32_cond_wait_cleanup_args_t;
1226
1227static void
1228ptw32_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
1305static int
1306ptw32_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
1406static int
1407ptw32_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
1463completes
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
1479semaphore
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
1495too
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
1507int
1508pthread_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
1516int
1517pthread_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
1530int
1531pthread_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
1537int
1538pthread_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
1547TEREKHOV@de.ibm.com on 17.01.2001 01:00:57
1548
1549Please respond to TEREKHOV@de.ibm.com
1550
1551To: pthreads-win32@sourceware.cygnus.com
1552cc: schmidt@uci.edu
1553Subject: win32 conditions: sem+counter+event = broadcast_deadlock +
1554 spur.wakeup/unfairness/incorrectness ??
1555
1556
1557
1558
1559
1560
1561
1562Hi,
1563
1564Problem 1: broadcast_deadlock
1565
1566It seems that current implementation does not provide "atomic"
1567broadcasts. That may lead to "nested" broadcasts... and it seems
1568that nested case is not handled correctly -> producing a broadcast
1569DEADLOCK as a result.
1570
1571Scenario:
1572
1573N (>1) waiting threads W1..N are blocked (in _wait) on condition's
1574semaphore.
1575
1576Thread B1 calls pthread_cond_broadcast, which results in "releasing" N
1577W threads via incrementing semaphore counter by N (stored in
1578cv->waiters) BUT cv->waiters counter does not change!! The caller
1579thread B1 remains blocked on cv->waitersDone event (auto-reset!!) BUT
1580condition is not protected from starting another broadcast (when called
1581on another thread) while still waiting for the "old" broadcast to
1582complete on thread B1.
1583
1584M (>=0, <N) W threads are fast enough to go thru their _wait call and
1585decrement cv->waiters counter.
1586
1587L (N-M) "late" waiter W threads are a) still blocked/not returned from
1588their semaphore wait call or b) were preempted after sem_wait but before
1589lock( &cv->waitersLock ) or c) are blocked on cv->waitersLock.
1590
1591cv->waiters is still > 0 (= L).
1592
1593Another thread B2 (or some W thread from M group) calls
1594pthread_cond_broadcast and gains access to counter... neither a) nor b)
1595prevent thread B2 in pthread_cond_broadcast from gaining access to
1596counter and starting another broadcast ( for c) - it depends on
1597cv->waitersLock scheduling rules: FIFO=OK, PRTY=PROBLEM,... )
1598
1599That call to pthread_cond_broadcast (on thread B2) will result in
1600incrementing semaphore by cv->waiters (=L) which is INCORRECT (all
1601W1..N were in fact already released by thread B1) and waiting on
1602_auto-reset_ event cv->waitersDone which is DEADLY WRONG (produces a
1603deadlock)...
1604
1605All late W1..L threads now have a chance to complete their _wait call.
1606Last W_L thread sets an auto-reselt event cv->waitersDone which will
1607release either B1 or B2 leaving one of B threads in a deadlock.
1608
1609Problem 2: spur.wakeup/unfairness/incorrectness
1610
1611It seems that:
1612
1613a) because of the same problem with counter which does not reflect the
1614actual number of NOT RELEASED waiters, the signal call may increment
1615a semaphore counter w/o having a waiter blocked on it. That will result
1616in (best case) spurious wake ups - performance degradation due to
1617unnecessary context switches and predicate re-checks and (in worth case)
1618unfairness/incorrectness problem - see b)
1619
1620b) 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
1623from sem_wait on condition's semaphore). Win semaphore just [API DOC]:
1624"Maintains a count between zero and some maximum value, limiting the number
1625of threads that are simultaneously accessing a shared resource." Calling
1626ReleaseSemaphore does not imply (at least not documented) that on return
1627from ReleaseSemaphore all waiters will in fact become released (returned
1628from their Wait... call) and/or that new waiters calling Wait... afterwards
1629will become less importance. It is NOT documented to be an atomic release
1630of
1631waiters... And even if it would be there is still a problem with a thread
1632being preempted after Wait on semaphore and before Wait on cv->waitersLock
1633and scheduling rules for cv->waitersLock itself
1634(??WaitForMultipleObjects??)
1635That may result in unfairness/incorrectness problem as described
1636for SetEvent impl. in "Strategies for Implementing POSIX Condition
1637Variables
1638on Win32": http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
1639
1640Unfairness -- The semantics of the POSIX pthread_cond_broadcast function is
1641to wake up all threads currently blocked in wait calls on the condition
1642variable. The awakened threads then compete for the external_mutex. To
1643ensure
1644fairness, all of these threads should be released from their
1645pthread_cond_wait calls and allowed to recheck their condition expressions
1646before other threads can successfully complete a wait on the condition
1647variable.
1648
1649Unfortunately, the SetEvent implementation above does not guarantee that
1650all
1651threads sleeping on the condition variable when cond_broadcast is called
1652will
1653acquire the external_mutex and check their condition expressions. Although
1654the Pthreads specification does not mandate this degree of fairness, the
1655lack of fairness can cause starvation.
1656
1657To illustrate the unfairness problem, imagine there are 2 threads, C1 and
1658C2,
1659that are blocked in pthread_cond_wait on condition variable not_empty_ that
1660is guarding a thread-safe message queue. Another thread, P1 then places two
1661messages onto the queue and calls pthread_cond_broadcast. If C1 returns
1662from
1663pthread_cond_wait, dequeues and processes the message, and immediately
1664waits
1665again then it and only it may end up acquiring both messages. Thus, C2 will
1666never get a chance to dequeue a message and run.
1667
1668The following illustrates the sequence of events:
1669
16701. Thread C1 attempts to dequeue and waits on CV non_empty_
16712. Thread C2 attempts to dequeue and waits on CV non_empty_
16723. Thread P1 enqueues 2 messages and broadcasts to CV not_empty_
16734. Thread P1 exits
16745. Thread C1 wakes up from CV not_empty_, dequeues a message and runs
16756. Thread C1 waits again on CV not_empty_, immediately dequeues the 2nd
1676 message and runs
16777. Thread C1 exits
16788. Thread C2 is the only thread left and blocks forever since
1679 not_empty_ will never be signaled
1680
1681Depending on the algorithm being implemented, this lack of fairness may
1682yield
1683concurrent programs that have subtle bugs. Of course, application
1684developers
1685should not rely on the fairness semantics of pthread_cond_broadcast.
1686However,
1687there are many cases where fair implementations of condition variables can
1688simplify application code.
1689
1690Incorrectness -- A variation on the unfairness problem described above
1691occurs
1692when a third consumer thread, C3, is allowed to slip through even though it
1693was not waiting on condition variable not_empty_ when a broadcast occurred.
1694
1695To illustrate this, we will use the same scenario as above: 2 threads, C1
1696and
1697C2, are blocked dequeuing messages from the message queue. Another thread,
1698P1
1699then places two messages onto the queue and calls pthread_cond_broadcast.
1700C1
1701returns from pthread_cond_wait, dequeues and processes the message. At this
1702time, C3 acquires the external_mutex, calls pthread_cond_wait and waits on
1703the events in WaitForMultipleObjects. Since C2 has not had a chance to run
1704yet, the BROADCAST event is still signaled. C3 then returns from
1705WaitForMultipleObjects, and dequeues and processes the message in the
1706queue.
1707Thus, C2 will never get a chance to dequeue a message and run.
1708
1709The following illustrates the sequence of events:
1710
17111. Thread C1 attempts to dequeue and waits on CV non_empty_
17122. Thread C2 attempts to dequeue and waits on CV non_empty_
17133. Thread P1 enqueues 2 messages and broadcasts to CV not_empty_
17144. Thread P1 exits
17155. Thread C1 wakes up from CV not_empty_, dequeues a message and runs
17166. Thread C1 exits
17177. Thread C3 waits on CV not_empty_, immediately dequeues the 2nd
1718 message and runs
17198. Thread C3 exits
17209. Thread C2 is the only thread left and blocks forever since
1721 not_empty_ will never be signaled
1722
1723In the above case, a thread that was not waiting on the condition variable
1724when a broadcast occurred was allowed to proceed. This leads to incorrect
1725semantics for a condition variable.
1726
1727
1728COMMENTS???
1729
1730regards,
1731alexander.
1732
1733-----------------------------------------------------------------------------
1734
1735Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_*
1736 implementation questions
1737Date: Wed, 21 Feb 2001 11:54:47 +0100
1738From: TEREKHOV@de.ibm.com
1739To: lthomas@arbitrade.com
1740CC: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>,
1741 Nanbor Wang <nanbor@cs.wustl.edu>
1742
1743Hi Louis,
1744
1745generation number 8..
1746
1747had some time to revisit timeouts/spurious wakeup problem..
1748found some bugs (in 7.b/c/d) and something to improve
1749(7a - using IPC semaphores but it should speedup Win32
1750version as well).
1751
1752regards,
1753alexander.
1754
1755---------- Algorithm 8a / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ALL ------
1756given:
1757semBlockLock - bin.semaphore
1758semBlockQueue - semaphore
1759mtxExternal - mutex or CS
1760mtxUnblockLock - mutex or CS
1761nWaitersGone - int
1762nWaitersBlocked - int
1763nWaitersToUnblock - int
1764
1765wait( 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
1792again
1793 }
1794 else if ( 0 != (nWaitersWasGone = nWaitersGone) ) {
1795 nWaitersGone = 0;
1796 }
1797 }
1798 }
1799 else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
1800semaphore :-)
1801 sem_wait( semBlockLock );
1802 nWaitersBlocked -= nWaitersGone; // something is going on here -
1803test 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
1814later
1815 }
1816 }
1817 sem_post( semBlockLock ); // open the gate
1818 }
1819
1820 lock( mtxExternal );
1821
1822 return ( bTimedOut ) ? ETIMEOUT : 0;
1823}
1824
1825signal(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------
1872given:
1873semBlockLock - bin.semaphore
1874semBlockQueue - bin.semaphore
1875mtxExternal - mutex or CS
1876mtxUnblockLock - mutex or CS
1877nWaitersGone - int
1878nWaitersBlocked - int
1879nWaitersToUnblock - int
1880
1881wait( 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
1900below (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
1910again
1911 }
1912 else if ( 0 != (nWaitersWasGone = nWaitersGone) ) {
1913 nWaitersGone = 0;
1914 }
1915 }
1916 }
1917 else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
1918semaphore :-)
1919 sem_wait( semBlockLock );
1920 nWaitersBlocked -= nWaitersGone; // something is going on here -
1921test 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
1931later
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
1944signal(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---------
1990given:
1991hevBlockLock - auto-reset event
1992hevBlockQueue - auto-reset event
1993mtxExternal - mutex or CS
1994mtxUnblockLock - mutex or CS
1995nWaitersGone - int
1996nWaitersBlocked - int
1997nWaitersToUnblock - int
1998
1999wait( 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
2018below (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
2028again
2029 }
2030 else if ( 0 != (nWaitersWasGone = nWaitersGone) ) {
2031 nWaitersGone = 0;
2032 }
2033 }
2034 }
2035 else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
2036event :-)
2037 wait( hevBlockLock,INFINITE );
2038 nWaitersBlocked -= nWaitersGone; // something is going on here -
2039test 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
2048later
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
2061signal(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 ------
2106given:
2107hevBlockLock - auto-reset event
2108hevBlockQueueS - auto-reset event // for signals
2109hevBlockQueueB - manual-reset even // for broadcasts
2110mtxExternal - mutex or CS
2111mtxUnblockLock - mutex or CS
2112eBroadcast - int // 0: no broadcast, 1: broadcast, 2:
2113broadcast after signal(s)
2114nWaitersGone - int
2115nWaitersBlocked - int
2116nWaitersToUnblock - int
2117
2118wait( 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
2138below (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
2148again
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
2161below (already unblocked)
2162 }
2163 }
2164 else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or spurious
2165event :-)
2166 wait( hevBlockLock,INFINITE );
2167 nWaitersBlocked -= nWaitersGone; // something is going on here -
2168test 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
2180later
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
2193signal(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
224302/21/2001 09:13 AM ---------------------------
2244
2245Alexander Terekhov
224602/20/2001 04:33 PM
2247
2248To: Louis Thomas <lthomas@arbitrade.com>
2249cc:
2250
2251From: Alexander Terekhov/Germany/IBM@IBMDE
2252Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
2253 n questions
2254Importance: 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
2260ok. no problem. here is some more stuff for pauses you might have
2261in between :)
2262
2263---------- Algorithm 7d / IMPL_EVENT,UNBLOCK_STRATEGY == UNBLOCK_ALL ------
2264given:
2265hevBlockLock - auto-reset event
2266hevBlockQueueS - auto-reset event // for signals
2267hevBlockQueueB - manual-reset even // for broadcasts
2268mtxExternal - mutex or CS
2269mtxUnblockLock - mutex or CS
2270bBroadcast - int
2271nWaitersGone - int
2272nWaitersBlocked - int
2273nWaitersToUnblock - int
2274
2275wait( 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
2294below (already unblocked)
2295 }
2296 else if ( !bBroadcast ) {
2297 wait( hevBlockQueueS,INFINITE ); // better now than spurious
2298later
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
2309again
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
2320event :-)
2321 wait( hevBlockLock,INFINITE );
2322 nWaitersBlocked -= nWaitersGone; // something is going on here -
2323test 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
2344signal(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
2397Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
2398 n questions
2399Date: Mon, 26 Feb 2001 22:20:12 -0600
2400From: Louis Thomas <lthomas@arbitrade.com>
2401To: "'TEREKHOV@de.ibm.com'" <TEREKHOV@de.ibm.com>
2402CC: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>,
2403 Nanbor Wang
2404 <nanbor@cs.wustl.edu>
2405
2406Sorry all. Busy week.
2407
2408> this insures the fairness
2409> which POSIX does not (e.g. two subsequent broadcasts - the gate does
2410insure
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
2415I'm not sure how we are any more fair about this than Linux. We certainly
2416don't guarantee that the threads released by the first broadcast will get
2417the external mutex before the threads of the second wave. In fact, it is
2418possible that those threads will never get the external mutex if there is
2419enough contention for it.
2420
2421> e.g. i was thinking about implementation with a pool of
2422> N semaphores/counters [...]
2423
2424I considered that too. The problem is as you mentioned in a). You really
2425need to assign threads to semaphores once you know how you want to wake them
2426up, not when they first begin waiting which is the only time you can assign
2427them.
2428
2429> well, i am not quite sure that i've fully understood your scenario,
2430
2431Hmm. Well, it think it's an important example, so I'll try again. First, we
2432have thread A which we KNOW is waiting on a condition. As soon as it becomes
2433unblocked for any reason, we will know because it will set a flag. Since the
2434flag is not set, we are 100% confident that thread A is waiting on the
2435condition. We have another thread, thread B, which has acquired the mutex
2436and is about to wait on the condition. Thus it is pretty clear that at any
2437point, either just A is waiting, or A and B are waiting. Now thread C comes
2438along. C is about to do a broadcast on the condition. A broadcast is
2439guaranteed to unblock all threads currently waiting on a condition, right?
2440Again, we said that either just A is waiting, or A and B are both waiting.
2441So, when C does its broadcast, depending upon whether B has started waiting
2442or not, thread C will unblock A or unblock A and B. Either way, C must
2443unblock A, right?
2444
2445Now, you said anything that happens is correct so long as a) "a signal is
2446not lost between unlocking the mutex and waiting on the condition" and b) "a
2447thread must not steal a signal it sent", correct? Requirement b) is easy to
2448satisfy: in this scenario, thread C will never wait on the condition, so it
2449won't steal any signals. Requirement a) is not hard either. The only way we
2450could fail to meet requirement a) in this scenario is if thread B was
2451started waiting but didn't wake up because a signal was lost. This will not
2452happen.
2453
2454Now, here is what happens. Assume thread C beats thread B. Thread C looks to
2455see how many threads are waiting on the condition. Thread C sees just one
2456thread, thread A, waiting. It does a broadcast waking up just one thread
2457because just one thread is waiting. Next, before A can become unblocked,
2458thread B begins waiting. Now there are two threads waiting, but only one
2459will be unblocked. Suppose B wins. B will become unblocked. A will not
2460become unblocked, because C only unblocked one thread (sema_post cond, 1).
2461So at the end, B finishes and A remains blocked.
2462
2463We have met both of your requirements, so by your rules, this is an
2464acceptable outcome. However, I think that the spec says this is an
2465unacceptable outcome! We know for certain that A was waiting and that C did
2466a broadcast, but A did not become unblocked! Yet, the spec says that a
2467broadcast wakes up all waiting threads. This did not happen. Do you agree
2468that this shows your rules are not strict enough?
2469
2470> and what about N2? :) this one does allow almost everything.
2471
2472Don't get me started about rule #2. I'll NEVER advocate an algorithm that
2473uses 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
2478You are correct. My mistake.
2479
2480> i would remove "_bTimedOut=false".. after all, it was a real timeout..
2481
2482I disagree. A thread that can't successfully retract its waiter status can't
2483really have timed out. If a thread can't return without executing extra code
2484to deal with the fact that someone tried to unblock it, I think it is a poor
2485idea to pretend we
2486didn't realize someone was trying to signal us. After all, a signal is more
2487important than a time out.
2488
2489> when nSignaled != 0, it is possible to update nWaiters (--) and do not
2490> touch nGone
2491
2492I realize this, but I was thinking that writing it the other ways saves
2493another if statement.
2494
2495> adjust only if nGone != 0 and save one cache memory write - probably much
2496slower than 'if'
2497
2498Hmm. 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
2502timing
2503> out and no signals at all).
2504
2505Also true. Not only that, but you also have the possibility that one could
2506overflow the number of waiters as well! However, considering the limit you
2507have chosen for nWaitersGone, I suppose it is unlikely that anyone would be
2508able to get INT_MAX/2 threads waiting on a single condition. :)
2509
2510Analysis of 8a:
2511
2512It looks correct to me.
2513
2514What are IPC semaphores?
2515
2516In the line where you state, "else if ( nWaitersBlocked > nWaitersGone ) {
2517// HARMLESS RACE CONDITION!" there is no race condition for nWaitersGone
2518because nWaitersGone is never modified without holding mtxUnblockLock. You
2519are correct that there is a harmless race on nWaitersBlocked, which can
2520increase and make the condition become true just after we check it. If this
2521happens, we interpret it as the wait starting after the signal.
2522
2523I like your optimization of this. You could improve Alg. 6 as follows:
2524---------- Algorithm 6b ----------
2525signal(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
2530counters.
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---------- ---------- ----------
2549I guess this wouldn't apply to Alg 8a because nWaitersGone changes meanings
2550depending upon whether the gate is open or closed.
2551
2552In the loop "while ( nWaitersWasGone-- ) {" you do a sema_wait on
2553semBlockLock. Perhaps waiting on semBlockQueue would be a better idea.
2554
2555What have you gained by making the last thread to be signaled do the waits
2556for all the timed out threads, besides added complexity? It took me a long
2557time to figure out what your objective was with this, to realize you were
2558using nWaitersGone to mean two different things, and to verify that you
2559hadn't introduced any bug by doing this. Even now I'm not 100% sure.
2560
2561What has all this playing about with nWaitersGone really gained us besides a
2562lot of complexity (it is much harder to verify that this solution is
2563correct), execution overhead (we now have a lot more if statements to
2564evaluate), and space overhead (more space for the extra code, and another
2565integer in our data)? We did manage to save a lock/unlock pair in an
2566uncommon case (when a time out occurs) at the above mentioned expenses in
2567the common cases.
2568
2569As for 8b, c, and d, they look ok though I haven't studied them thoroughly.
2570What would you use them for?
2571
2572 Later,
2573 -Louis! :)
2574
2575-----------------------------------------------------------------------------
2576
2577Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
2578 n questions
2579Date: Tue, 27 Feb 2001 15:51:28 +0100
2580From: TEREKHOV@de.ibm.com
2581To: Louis Thomas <lthomas@arbitrade.com>
2582CC: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>,
2583 Nanbor Wang <nanbor@cs.wustl.edu>
2584
2585Hi 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
2597correct. but gate is nevertheless more fair than Linux because of the
2598barrier it establishes between two races (1st and 2nd wave waiters) for
2599the mutex which under 'normal' circumstances (e.g. all threads of equal
2600priorities,..) will 'probably' result in fair behaviour with respect to
2601mutex 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
2607ok. now i seem to understand this example. well, now it seems to me
2608that the only meaningful rule is just:
2609
2610a) "a signal is not lost between unlocking the mutex and waiting on the
2611condition"
2612
2613and that the rule
2614
2615b) "a thread must not steal a signal it sent"
2616
2617is not needed at all because a thread which violates b) also violates a).
2618
2619i'll try to explain..
2620
2621i think that the most important thing is how POSIX defines waiter's
2622visibility:
2623
2624"if another thread is able to acquire the mutex after the about-to-block
2625thread
2626has released it, then a subsequent call to pthread_cond_signal() or
2627pthread_cond_broadcast() in that thread behaves as if it were issued after
2628the about-to-block thread has blocked. "
2629
2630my understanding is the following:
2631
26321) there is no guarantees whatsoever with respect to whether
2633signal/broadcast
2634will actually unblock any 'waiter' if it is done w/o acquiring the mutex
2635first
2636(note that a thread may release it before signal/broadcast - it does not
2637matter).
2638
26392) it is guaranteed that waiters become 'visible' - eligible for unblock as
2640soon
2641as signalling thread acquires the mutex (but not before!!)
2642
2643so..
2644
2645>So, when C does its broadcast, depending upon whether B has started
2646waiting
2647>or not, thread C will unblock A or unblock A and B. Either way, C must
2648>unblock A, right?
2649
2650right. but only if C did acquire the mutex prior to broadcast (it may
2651release it before broadcast as well).
2652
2653implementation will violate waiters visibility rule (signal will become
2654lost)
2655if C will not unblock A.
2656
2657>Now, here is what happens. Assume thread C beats thread B. Thread C looks
2658to
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
2667thread C did acquire the mutex ("Thread C sees just one thread, thread A,
2668waiting"). beginning from that moment it is guaranteed that subsequent
2669broadcast will unblock A. Otherwise we will have a lost signal with respect
2670to 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
2672it was simply lost with respect to A.
2673
2674>..Do you agree that this shows your rules are not strict enough?
2675
2676probably the opposite.. :-) i think that it shows that the only meaningful
2677rule is
2678
2679a) "a signal is not lost between unlocking the mutex and waiting on the
2680condition"
2681
2682with 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
2687can't
2688>really have timed out. If a thread can't return without executing extra
2689code
2690>to deal with the fact that someone tried to unblock it, I think it is a
2691poor
2692>idea to pretend we
2693>didn't realize someone was trying to signal us. After all, a signal is
2694more
2695>important than a time out.
2696
2697a) POSIX does allow timed out thread to consume a signal (cancelled is
2698not).
2699b) ETIMEDOUT status just says that: "The time specified by abstime to
2700pthread_cond_timedwait() has passed."
2701c) it seem to me that hiding timeouts would violate "The
2702pthread_cond_timedwait()
2703function is the same as pthread_cond_wait() except that an error is
2704returned if
2705the absolute time specified by abstime passes (that is, system time equals
2706or
2707exceeds abstime) before the condition cond is signaled or broadcasted"
2708because
2709the abs. time did really pass before cond was signaled (waiter was
2710released via semaphore). however, if it really matters, i could imaging
2711that we
2712can save an abs. time of signal/broadcast and compare it with timeout after
2713unblock to find out whether it was a 'real' timeout or not. absent this
2714check
2715i do think that hiding timeouts would result in technical violation of
2716specification.. but i think that this check is not important and we can
2717simply
2718trust 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>
2724int semctl(int, int, int, ...);
2725int semget(key_t, int, int);
2726int semop(int, struct sembuf *, size_t);
2727
2728they support adjustment of semaphore counter (semvalue)
2729in 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
2736this
2737>happens, we interpret it as the wait starting after the signal.
2738
2739well, the reason why i've asked on comp.programming.threads whether this
2740race
2741condition is harmless or not is that in order to be harmless it should not
2742violate the waiters visibility rule (see above). Fortunately, we increment
2743the counter under protection of external mutex.. so that any (signalling)
2744thread 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
2747Win32/INTEL
2748which 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
2756that
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
2778meanings
2779>depending upon whether the gate is open or closed.
2780
2781agree.
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
2786you 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
2795a
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
2803well, please consider the following:
2804
28051) with multiple waiters unblocked (but some timed out) the trick with
2806counter
2807seem to ensure potentially higher level of concurrency by not delaying
2808most of unblocked waiters for semaphore cleanup - only the last one
2809will be delayed but all others would already contend/acquire/release
2810the external mutex - the critical section protected by mtxUnblockLock is
2811made smaller (increment + couple of IFs is faster than system/kernel call)
2812which i think is good in general. however, you are right, this is done
2813at expense of 'normal' waiters..
2814
28152) some semaphore APIs (e.g. POSIX IPC sems) do allow to adjust the
2816semaphore counter in one call => less system/kernel calls.. imagine:
2817
2818if ( 1 == nSignalsWasLeft ) {
2819 if ( 0 != nWaitersWasGone ) {
2820 ReleaseSemaphore( semBlockQueue,-nWaitersWasGone ); // better now
2821than spurious later
2822 }
2823 sem_post( semBlockLock ); // open the gate
2824 }
2825
28263) even on win32 a single thread doing multiple cleanup calls (to wait)
2827will probably result in faster execution (because of processor caching)
2828than 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
2831thoroughly.
2832>What would you use them for?
2833
28348b) for semaphores which do not allow to unblock multiple waiters
2835in a single call to post/release (e.g. POSIX realtime semaphores -
2836<semaphore.h>)
2837
28388c/8d) for WinCE prior to 3.0 (WinCE 3.0 does have semaphores)
2839
2840ok. so, which one is the 'final' algorithm(s) which we should use in
2841pthreads-win32??
2842
2843regards,
2844alexander.
2845
2846----------------------------------------------------------------------------
2847
2848Louis Thomas <lthomas@arbitrade.com> on 02/27/2001 05:20:12 AM
2849
2850Please respond to Louis Thomas <lthomas@arbitrade.com>
2851
2852To: Alexander Terekhov/Germany/IBM@IBMDE
2853cc: rpj@ise.canberra.edu.au, Thomas Pfaff <tpfaff@gmx.net>, Nanbor Wang
2854 <nanbor@cs.wustl.edu>
2855Subject: RE: FYI/comp.programming.threads/Re: pthread_cond_* implementatio
2856 n questions
2857
2858Sorry all. Busy week.
2859
2860> this insures the fairness
2861> which POSIX does not (e.g. two subsequent broadcasts - the gate does
2862insure
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
2867I'm not sure how we are any more fair about this than Linux. We certainly
2868don't guarantee that the threads released by the first broadcast will get
2869the external mutex before the threads of the second wave. In fact, it is
2870possible that those threads will never get the external mutex if there is
2871enough contention for it.
2872
2873> e.g. i was thinking about implementation with a pool of
2874> N semaphores/counters [...]
2875
2876I considered that too. The problem is as you mentioned in a). You really
2877need to assign threads to semaphores once you know how you want to wake
2878them
2879up, not when they first begin waiting which is the only time you can assign
2880them.
2881
2882> well, i am not quite sure that i've fully understood your scenario,
2883
2884Hmm. Well, it think it's an important example, so I'll try again. First, we
2885have thread A which we KNOW is waiting on a condition. As soon as it
2886becomes
2887unblocked for any reason, we will know because it will set a flag. Since
2888the
2889flag is not set, we are 100% confident that thread A is waiting on the
2890condition. We have another thread, thread B, which has acquired the mutex
2891and is about to wait on the condition. Thus it is pretty clear that at any
2892point, either just A is waiting, or A and B are waiting. Now thread C comes
2893along. C is about to do a broadcast on the condition. A broadcast is
2894guaranteed to unblock all threads currently waiting on a condition, right?
2895Again, we said that either just A is waiting, or A and B are both waiting.
2896So, when C does its broadcast, depending upon whether B has started waiting
2897or not, thread C will unblock A or unblock A and B. Either way, C must
2898unblock A, right?
2899
2900Now, you said anything that happens is correct so long as a) "a signal is
2901not lost between unlocking the mutex and waiting on the condition" and b)
2902"a
2903thread must not steal a signal it sent", correct? Requirement b) is easy to
2904satisfy: in this scenario, thread C will never wait on the condition, so it
2905won't steal any signals. Requirement a) is not hard either. The only way
2906we
2907could fail to meet requirement a) in this scenario is if thread B was
2908started waiting but didn't wake up because a signal was lost. This will not
2909happen.
2910
2911Now, here is what happens. Assume thread C beats thread B. Thread C looks
2912to
2913see how many threads are waiting on the condition. Thread C sees just one
2914thread, thread A, waiting. It does a broadcast waking up just one thread
2915because just one thread is waiting. Next, before A can become unblocked,
2916thread B begins waiting. Now there are two threads waiting, but only one
2917will be unblocked. Suppose B wins. B will become unblocked. A will not
2918become unblocked, because C only unblocked one thread (sema_post cond, 1).
2919So at the end, B finishes and A remains blocked.
2920
2921We have met both of your requirements, so by your rules, this is an
2922acceptable outcome. However, I think that the spec says this is an
2923unacceptable outcome! We know for certain that A was waiting and that C did
2924a broadcast, but A did not become unblocked! Yet, the spec says that a
2925broadcast wakes up all waiting threads. This did not happen. Do you agree
2926that this shows your rules are not strict enough?
2927
2928> and what about N2? :) this one does allow almost everything.
2929
2930Don't get me started about rule #2. I'll NEVER advocate an algorithm that
2931uses 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
2936You are correct. My mistake.
2937
2938> i would remove "_bTimedOut=false".. after all, it was a real timeout..
2939
2940I disagree. A thread that can't successfully retract its waiter status
2941can't
2942really have timed out. If a thread can't return without executing extra
2943code
2944to deal with the fact that someone tried to unblock it, I think it is a
2945poor
2946idea to pretend we
2947didn't realize someone was trying to signal us. After all, a signal is more
2948important than a time out.
2949
2950> when nSignaled != 0, it is possible to update nWaiters (--) and do not
2951> touch nGone
2952
2953I realize this, but I was thinking that writing it the other ways saves
2954another if statement.
2955
2956> adjust only if nGone != 0 and save one cache memory write - probably much
2957slower than 'if'
2958
2959Hmm. 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
2963timing
2964> out and no signals at all).
2965
2966Also true. Not only that, but you also have the possibility that one could
2967overflow the number of waiters as well! However, considering the limit you
2968have chosen for nWaitersGone, I suppose it is unlikely that anyone would be
2969able to get INT_MAX/2 threads waiting on a single condition. :)
2970
2971Analysis of 8a:
2972
2973It looks correct to me.
2974
2975What are IPC semaphores?
2976
2977In the line where you state, "else if ( nWaitersBlocked > nWaitersGone ) {
2978// HARMLESS RACE CONDITION!" there is no race condition for nWaitersGone
2979because nWaitersGone is never modified without holding mtxUnblockLock. You
2980are correct that there is a harmless race on nWaitersBlocked, which can
2981increase and make the condition become true just after we check it. If this
2982happens, we interpret it as the wait starting after the signal.
2983
2984I like your optimization of this. You could improve Alg. 6 as follows:
2985---------- Algorithm 6b ----------
2986signal(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
2991counters.
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---------- ---------- ----------
3010I guess this wouldn't apply to Alg 8a because nWaitersGone changes meanings
3011depending upon whether the gate is open or closed.
3012
3013In the loop "while ( nWaitersWasGone-- ) {" you do a sema_wait on
3014semBlockLock. Perhaps waiting on semBlockQueue would be a better idea.
3015
3016What have you gained by making the last thread to be signaled do the waits
3017for all the timed out threads, besides added complexity? It took me a long
3018time to figure out what your objective was with this, to realize you were
3019using nWaitersGone to mean two different things, and to verify that you
3020hadn't introduced any bug by doing this. Even now I'm not 100% sure.
3021
3022What has all this playing about with nWaitersGone really gained us besides
3023a
3024lot of complexity (it is much harder to verify that this solution is
3025correct), execution overhead (we now have a lot more if statements to
3026evaluate), and space overhead (more space for the extra code, and another
3027integer in our data)? We did manage to save a lock/unlock pair in an
3028uncommon case (when a time out occurs) at the above mentioned expenses in
3029the common cases.
3030
3031As for 8b, c, and d, they look ok though I haven't studied them thoroughly.
3032What would you use them for?
3033
3034 Later,
3035 -Louis! :)
3036
3037
README.NONPORTABLE
1This file documents non-portable functions and other issues.
2
3Non-portable functions included in pthreads-win32
4-------------------------------------------------
5
6BOOL
7pthread_win32_test_features_np(int mask)
8
9 This routine allows an application to check which
10 run-time auto-detected features are available within
11 the library.
12
13 The possible features are:
14
15 PTW32_SYSTEM_INTERLOCKED_COMPARE_EXCHANGE
16 Return TRUE if the native version of
17 InterlockedCompareExchange() is being used.
18 This feature is not meaningful in recent
19 library versions as MSVC builds only support
20 system implemented ICE. Note that all Mingw
21 builds use inlined asm versions of all the
22 Interlocked routines.
23 PTW32_ALERTABLE_ASYNC_CANCEL
24 Return TRUE is the QueueUserAPCEx package
25 QUSEREX.DLL is available and the AlertDrv.sys
26 driver is loaded into Windows, providing
27 alertable (pre-emptive) asyncronous threads
28 cancelation. If this feature returns FALSE
29 then the default async cancel scheme is in
30 use, which cannot cancel blocked threads.
31
32 Features may be Or'ed into the mask parameter, in which case
33 the routine returns TRUE if any of the Or'ed features would
34 return TRUE. At this stage it doesn't make sense to Or features
35 but it may some day.
36
37
38void *
39pthread_timechange_handler_np(void *)
40
41 To improve tolerance against operator or time service
42 initiated system clock changes.
43
44 This routine can be called by an application when it
45 receives a WM_TIMECHANGE message from the system. At
46 present it broadcasts all condition variables so that
47 waiting threads can wake up and re-evaluate their
48 conditions and restart their timed waits if required.
49
50 It has the same return type and argument type as a
51 thread routine so that it may be called directly
52 through pthread_create(), i.e. as a separate thread.
53
54 Parameters
55
56 Although a parameter must be supplied, it is ignored.
57 The value NULL can be used.
58
59 Return values
60
61 It can return an error EAGAIN to indicate that not
62 all condition variables were broadcast for some reason.
63 Otherwise, 0 is returned.
64
65 If run as a thread, the return value is returned
66 through pthread_join().
67
68 The return value should be cast to an integer.
69
70
71HANDLE
72pthread_getw32threadhandle_np(pthread_t thread);
73
74 Returns the win32 thread handle that the POSIX
75 thread "thread" is running as.
76
77 Applications can use the win32 handle to set
78 win32 specific attributes of the thread.
79
80DWORD
81pthread_getw32threadid_np (pthread_t thread)
82
83 Returns the Windows native thread ID that the POSIX
84 thread "thread" is running as.
85
86 Only valid when the library is built where
87 ! (defined(__MINGW64__) || defined(__MINGW32__)) || defined (__MSVCRT__) || defined (__DMC__)
88 and otherwise returns 0.
89
90
91int
92pthread_mutexattr_setkind_np(pthread_mutexattr_t * attr, int kind)
93
94int
95pthread_mutexattr_getkind_np(pthread_mutexattr_t * attr, int *kind)
96
97 These two routines are included for Linux compatibility
98 and are direct equivalents to the standard routines
99 pthread_mutexattr_settype
100 pthread_mutexattr_gettype
101
102 pthread_mutexattr_setkind_np accepts the following
103 mutex kinds:
104 PTHREAD_MUTEX_FAST_NP
105 PTHREAD_MUTEX_ERRORCHECK_NP
106 PTHREAD_MUTEX_RECURSIVE_NP
107
108 These are really just equivalent to (respectively):
109 PTHREAD_MUTEX_NORMAL
110 PTHREAD_MUTEX_ERRORCHECK
111 PTHREAD_MUTEX_RECURSIVE
112
113int
114pthread_delay_np (const struct timespec *interval);
115
116 This routine causes a thread to delay execution for a specific period of time.
117 This period ends at the current time plus the specified interval. The routine
118 will not return before the end of the period is reached, but may return an
119 arbitrary amount of time after the period has gone by. This can be due to
120 system load, thread priorities, and system timer granularity.
121
122 Specifying an interval of zero (0) seconds and zero (0) nanoseconds is
123 allowed and can be used to force the thread to give up the processor or to
124 deliver a pending cancelation request.
125
126 This routine is a cancelation point.
127
128 The timespec structure contains the following two fields:
129
130 tv_sec is an integer number of seconds.
131 tv_nsec is an integer number of nanoseconds.
132
133 Return Values
134
135 If an error condition occurs, this routine returns an integer value
136 indicating the type of error. Possible return values are as follows:
137
138 0 Successful completion.
139 [EINVAL] The value specified by interval is invalid.
140
141int
142pthread_num_processors_np (void)
143
144 This routine (found on HPUX systems) returns the number of processors
145 in the system. This implementation actually returns the number of
146 processors available to the process, which can be a lower number
147 than the system's number, depending on the process's affinity mask.
148
149BOOL
150pthread_win32_process_attach_np (void);
151
152BOOL
153pthread_win32_process_detach_np (void);
154
155BOOL
156pthread_win32_thread_attach_np (void);
157
158BOOL
159pthread_win32_thread_detach_np (void);
160
161 These functions contain the code normally run via dllMain
162 when the library is used as a dll but which need to be
163 called explicitly by an application when the library
164 is statically linked. As of version 2.9.0 of the library, static
165 builds using either MSC or GCC will call pthread_win32_process_*
166 automatically at application startup and exit respectively.
167
168 Otherwise, you will need to call pthread_win32_process_attach_np()
169 before you can call any pthread routines when statically linking.
170 You should call pthread_win32_process_detach_np() before
171 exiting your application to clean up.
172
173 pthread_win32_thread_attach_np() is currently a no-op, but
174 pthread_win32_thread_detach_np() is needed to clean up
175 the implicit pthread handle that is allocated to a Win32 thread if
176 it calls any pthreads routines. Call this routine when the
177 Win32 thread exits.
178
179 Threads created through pthread_create() do not need to call
180 pthread_win32_thread_detach_np().
181
182 These functions invariably return TRUE except for
183 pthread_win32_process_attach_np() which will return FALSE
184 if pthreads-win32 initialisation fails.
185
186int
187pthreadCancelableWait (HANDLE waitHandle);
188
189int
190pthreadCancelableTimedWait (HANDLE waitHandle, DWORD timeout);
191
192 These two functions provide hooks into the pthread_cancel
193 mechanism that will allow you to wait on a Windows handle
194 and make it a cancellation point. Both functions block
195 until either the given w32 handle is signaled, or
196 pthread_cancel has been called. It is implemented using
197 WaitForMultipleObjects on 'waitHandle' and a manually
198 reset w32 event used to implement pthread_cancel.
199
200
201Non-portable issues
202-------------------
203
204Thread priority
205
206 POSIX defines a single contiguous range of numbers that determine a
207 thread's priority. Win32 defines priority classes and priority
208 levels relative to these classes. Classes are simply priority base
209 levels that the defined priority levels are relative to such that,
210 changing a process's priority class will change the priority of all
211 of it's threads, while the threads retain the same relativity to each
212 other.
213
214 A Win32 system defines a single contiguous monotonic range of values
215 that define system priority levels, just like POSIX. However, Win32
216 restricts individual threads to a subset of this range on a
217 per-process basis.
218
219 The following table shows the base priority levels for combinations
220 of priority class and priority value in Win32.
221
222 Process Priority Class Thread Priority Level
223 -----------------------------------------------------------------
224 1 IDLE_PRIORITY_CLASS THREAD_PRIORITY_IDLE
225 1 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_IDLE
226 1 NORMAL_PRIORITY_CLASS THREAD_PRIORITY_IDLE
227 1 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_IDLE
228 1 HIGH_PRIORITY_CLASS THREAD_PRIORITY_IDLE
229 2 IDLE_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
230 3 IDLE_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
231 4 IDLE_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
232 4 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
233 5 IDLE_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
234 5 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
235 5 Background NORMAL_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
236 6 IDLE_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
237 6 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
238 6 Background NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
239 7 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
240 7 Background NORMAL_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
241 7 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
242 8 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
243 8 NORMAL_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
244 8 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
245 8 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
246 9 NORMAL_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
247 9 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
248 9 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
249 10 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
250 10 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
251 11 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
252 11 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
253 11 HIGH_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
254 12 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
255 12 HIGH_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
256 13 HIGH_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
257 14 HIGH_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
258 15 HIGH_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
259 15 HIGH_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
260 15 IDLE_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
261 15 BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
262 15 NORMAL_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
263 15 ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
264 16 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_IDLE
265 17 REALTIME_PRIORITY_CLASS -7
266 18 REALTIME_PRIORITY_CLASS -6
267 19 REALTIME_PRIORITY_CLASS -5
268 20 REALTIME_PRIORITY_CLASS -4
269 21 REALTIME_PRIORITY_CLASS -3
270 22 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
271 23 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
272 24 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
273 25 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
274 26 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
275 27 REALTIME_PRIORITY_CLASS 3
276 28 REALTIME_PRIORITY_CLASS 4
277 29 REALTIME_PRIORITY_CLASS 5
278 30 REALTIME_PRIORITY_CLASS 6
279 31 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
280
281 Windows NT: Values -7, -6, -5, -4, -3, 3, 4, 5, and 6 are not supported.
282
283
284 As you can see, the real priority levels available to any individual
285 Win32 thread are non-contiguous.
286
287 An application using pthreads-win32 should not make assumptions about
288 the numbers used to represent thread priority levels, except that they
289 are monotonic between the values returned by sched_get_priority_min()
290 and sched_get_priority_max(). E.g. Windows 95, 98, NT, 2000, XP make
291 available a non-contiguous range of numbers between -15 and 15, while
292 at least one version of WinCE (3.0) defines the minimum priority
293 (THREAD_PRIORITY_LOWEST) as 5, and the maximum priority
294 (THREAD_PRIORITY_HIGHEST) as 1.
295
296 Internally, pthreads-win32 maps any priority levels between
297 THREAD_PRIORITY_IDLE and THREAD_PRIORITY_LOWEST to THREAD_PRIORITY_LOWEST,
298 or between THREAD_PRIORITY_TIME_CRITICAL and THREAD_PRIORITY_HIGHEST to
299 THREAD_PRIORITY_HIGHEST. Currently, this also applies to
300 REALTIME_PRIORITY_CLASSi even if levels -7, -6, -5, -4, -3, 3, 4, 5, and 6
301 are supported.
302
303 If it wishes, a Win32 application using pthreads-win32 can use the Win32
304 defined priority macros THREAD_PRIORITY_IDLE through
305 THREAD_PRIORITY_TIME_CRITICAL.
306
307
308The opacity of the pthread_t datatype
309-------------------------------------
310and possible solutions for portable null/compare/hash, etc
311----------------------------------------------------------
312
313Because pthread_t is an opague datatype an implementation is permitted to define
314pthread_t in any way it wishes. That includes defining some bits, if it is
315scalar, or members, if it is an aggregate, to store information that may be
316extra to the unique identifying value of the ID. As a result, pthread_t values
317may not be directly comparable.
318
319If you want your code to be portable you must adhere to the following contraints:
320
3211) Don't assume it is a scalar data type, e.g. an integer or pointer value. There
322are several other implementations where pthread_t is also a struct. See our FAQ
323Question 11 for our reasons for defining pthread_t as a struct.
324
3252) You must not compare them using relational or equality operators. You must use
326the API function pthread_equal() to test for equality.
327
3283) Never attempt to reference individual members.
329
330
331The problem
332
333Certain applications would like to be able to access only the 'pure' pthread_t
334id values, primarily to use as keys into data structures to manage threads or
335thread-related data, but this is not possible in a maximally portable and
336standards compliant way for current POSIX threads implementations.
337
338For implementations that define pthread_t as a scalar, programmers often employ
339direct relational and equality operators on pthread_t. This code will break when
340ported to an implementation that defines pthread_t as an aggregate type.
341
342For implementations that define pthread_t as an aggregate, e.g. a struct,
343programmers can use memcmp etc., but then face the prospect that the struct may
344include alignment padding bytes or bits as well as extra implementation-specific
345members that are not part of the unique identifying value.
346
347[While this is not currently the case for pthreads-win32, opacity also
348means that an implementation is free to change the definition, which should
349generally only require that applications be recompiled and relinked, not
350rewritten.]
351
352
353Doesn't the compiler take care of padding?
354
355The C89 and later standards only effectively guarrantee element-by-element
356equivalence following an assignment or pass by value of a struct or union,
357therefore undefined areas of any two otherwise equivalent pthread_t instances
358can still compare differently, e.g. attempting to compare two such pthread_t
359variables byte-by-byte, e.g. memcmp(&t1, &t2, sizeof(pthread_t) may give an
360incorrect result. In practice I'm reasonably confident that compilers routinely
361also copy the padding bytes, mainly because assignment of unions would be far
362too complicated otherwise. But it just isn't guarranteed by the standard.
363
364Illustration:
365
366We have two thread IDs t1 and t2
367
368pthread_t t1, t2;
369
370In an application we create the threads and intend to store the thread IDs in an
371ordered data structure (linked list, tree, etc) so we need to be able to compare
372them in order to insert them initially and also to traverse.
373
374Suppose pthread_t contains undefined padding bits and our compiler copies our
375pthread_t [struct] element-by-element, then for the assignment:
376
377pthread_t temp = t1;
378
379temp and t1 will be equivalent and correct but a byte-for-byte comparison such as
380memcmp(&temp, &t1, sizeof(pthread_t)) == 0 may not return true as we expect because
381the undefined bits may not have the same values in the two variable instances.
382
383Similarly if passing by value under the same conditions.
384
385If, on the other hand, the undefined bits are at least constant through every
386assignment and pass-by-value then the byte-for-byte comparison
387memcmp(&temp, &t1, sizeof(pthread_t)) == 0 will always return the expected result.
388How can we force the behaviour we need?
389
390
391Solutions
392
393Adding new functions to the standard API or as non-portable extentions is
394the only reliable and portable way to provide the necessary operations.
395Remember also that POSIX is not tied to the C language. The most common
396functions that have been suggested are:
397
398pthread_null()
399pthread_compare()
400pthread_hash()
401
402A single more general purpose function could also be defined as a
403basis for at least the last two of the above functions.
404
405First we need to list the freedoms and constraints with restpect
406to pthread_t so that we can be sure our solution is compatible with the
407standard.
408
409What is known or may be deduced from the standard:
4101) pthread_t must be able to be passed by value, so it must be a single object.
4112) from (1) it must be copyable so cannot embed thread-state information, locks
412or other volatile objects required to manage the thread it associates with.
4133) pthread_t may carry additional information, e.g. for debugging or to manage
414itself.
4154) there is an implicit requirement that the size of pthread_t is determinable
416at compile-time and size-invariant, because it must be able to copy the object
417(i.e. through assignment and pass-by-value). Such copies must be genuine
418duplicates, not merely a copy of a pointer to a common instance such as
419would be the case if pthread_t were defined as an array.
420
421
422Suppose we define the following function:
423
424/* This function shall return it's argument */
425pthread_t* pthread_normalize(pthread_t* thread);
426
427For scalar or aggregate pthread_t types this function would simply zero any bits
428within the pthread_t that don't uniquely identify the thread, including padding,
429such that client code can return consistent results from operations done on the
430result. If the additional bits are a pointer to an associate structure then
431this function would ensure that the memory used to store that associate
432structure does not leak. After normalization the following compare would be
433valid and repeatable:
434
435memcmp(pthread_normalize(&t1),pthread_normalize(&t2),sizeof(pthread_t))
436
437Note 1: such comparisons are intended merely to order and sort pthread_t values
438and allow them to index various data structures. They are not intended to reveal
439anything about the relationships between threads, like startup order.
440
441Note 2: the normalized pthread_t is also a valid pthread_t that uniquely
442identifies the same thread.
443
444Advantages:
4451) In most existing implementations this function would reduce to a no-op that
446emits no additional instructions, i.e after in-lining or optimisation, or if
447defined as a macro:
448#define pthread_normalise(tptr) (tptr)
449
4502) This single function allows an application to portably derive
451application-level versions of any of the other required functions.
452
4533) It is a generic function that could enable unanticipated uses.
454
455Disadvantages:
4561) Less efficient than dedicated compare or hash functions for implementations
457that include significant extra non-id elements in pthread_t.
458
4592) Still need to be concerned about padding if copying normalized pthread_t.
460See the later section on defining pthread_t to neutralise padding issues.
461
462Generally a pthread_t may need to be normalized every time it is used,
463which could have a significant impact. However, this is a design decision
464for the implementor in a competitive environment. An implementation is free
465to define a pthread_t in a way that minimises or eliminates padding or
466renders this function a no-op.
467
468Hazards:
4691) Pass-by-reference directly modifies 'thread' so the application must
470synchronise access or ensure that the pointer refers to a copy. The alternative
471of pass-by-value/return-by-value was considered but then this requires two copy
472operations, disadvantaging implementations where this function is not a no-op
473in terms of speed of execution. This function is intended to be used in high
474frequency situations and needs to be efficient, or at least not unnecessarily
475inefficient. The alternative also sits awkwardly with functions like memcmp.
476
4772) [Non-compliant] code that uses relational and equality operators on
478arithmetic or pointer style pthread_t types would need to be rewritten, but it
479should be rewritten anyway.
480
481
482C implementation of null/compare/hash functions using pthread_normalize():
483
484/* In pthread.h */
485pthread_t* pthread_normalize(pthread_t* thread);
486
487/* In user code */
488/* User-level bitclear function - clear bits in loc corresponding to mask */
489void* bitclear (void* loc, void* mask, size_t count);
490
491typedef unsigned int hash_t;
492
493/* User-level hash function */
494hash_t hash(void* ptr, size_t count);
495
496/*
497 * User-level pthr_null function - modifies the origin thread handle.
498 * The concept of a null pthread_t is highly implementation dependent
499 * and this design may be far from the mark. For example, in an
500 * implementation "null" may mean setting a special value inside one
501 * element of pthread_t to mean "INVALID". However, if that value was zero and
502 * formed part of the id component then we may get away with this design.
503 */
504pthread_t* pthr_null(pthread_t* tp)
505{
506 /*
507 * This should have the same effect as memset(tp, 0, sizeof(pthread_t))
508 * We're just showing that we can do it.
509 */
510 void* p = (void*) pthread_normalize(tp);
511 return (pthread_t*) bitclear(p, p, sizeof(pthread_t));
512}
513
514/*
515 * Safe user-level pthr_compare function - modifies temporary thread handle copies
516 */
517int pthr_compare_safe(pthread_t thread1, pthread_t thread2)
518{
519 return memcmp(pthread_normalize(&thread1), pthread_normalize(&thread2), sizeof(pthread_t));
520}
521
522/*
523 * Fast user-level pthr_compare function - modifies origin thread handles
524 */
525int pthr_compare_fast(pthread_t* thread1, pthread_t* thread2)
526{
527 return memcmp(pthread_normalize(&thread1), pthread_normalize(&thread2), sizeof(pthread_t));
528}
529
530/*
531 * Safe user-level pthr_hash function - modifies temporary thread handle copy
532 */
533hash_t pthr_hash_safe(pthread_t thread)
534{
535 return hash((void *) pthread_normalize(&thread), sizeof(pthread_t));
536}
537
538/*
539 * Fast user-level pthr_hash function - modifies origin thread handle
540 */
541hash_t pthr_hash_fast(pthread_t thread)
542{
543 return hash((void *) pthread_normalize(&thread), sizeof(pthread_t));
544}
545
546/* User-level bitclear function - modifies the origin array */
547void* bitclear(void* loc, void* mask, size_t count)
548{
549 int i;
550 for (i=0; i < count; i++) {
551 (unsigned char) *loc++ &= ~((unsigned char) *mask++);
552 }
553}
554
555/* Donald Knuth hash */
556hash_t hash(void* str, size_t count)
557{
558 hash_t hash = (hash_t) count;
559 unsigned int i = 0;
560
561 for(i = 0; i < len; str++, i++)
562 {
563 hash = ((hash << 5) ^ (hash >> 27)) ^ (*str);
564 }
565 return hash;
566}
567
568/* Example of advantage point (3) - split a thread handle into its id and non-id values */
569pthread_t id = thread, non-id = thread;
570bitclear((void*) &non-id, (void*) pthread_normalize(&id), sizeof(pthread_t));
571
572
573A pthread_t type change proposal to neutralise the effects of padding
574
575Even if pthread_nornalize() is available, padding is still a problem because
576the standard only garrantees element-by-element equivalence through
577copy operations (assignment and pass-by-value). So padding bit values can
578still change randomly after calls to pthread_normalize().
579
580[I suspect that most compilers take the easy path and always byte-copy anyway,
581partly because it becomes too complex to do (e.g. unions that contain sub-aggregates)
582but also because programmers can easily design their aggregates to minimise and
583often eliminate padding].
584
585How can we eliminate the problem of padding bytes in structs? Could
586defining pthread_t as a union rather than a struct provide a solution?
587
588In fact, the Linux pthread.h defines most of it's pthread_*_t objects (but not
589pthread_t itself) as unions, possibly for this and/or other reasons. We'll
590borrow some element naming from there but the ideas themselves are well known
591- the __align element used to force alignment of the union comes from K&R's
592storage allocator example.
593
594/* Essentially our current pthread_t renamed */
595typedef struct {
596 struct thread_state_t * __p;
597 long __x; /* sequence counter */
598} thread_id_t;
599
600Ensuring that the last element in the above struct is a long ensures that the
601overall struct size is a multiple of sizeof(long), so there should be no trailing
602padding in this struct or the union we define below.
603(Later we'll see that we can handle internal but not trailing padding.)
604
605/* New pthread_t */
606typedef union {
607 char __size[sizeof(thread_id_t)]; /* array as the first element */
608 thread_id_t __tid;
609 long __align; /* Ensure that the union starts on long boundary */
610} pthread_t;
611
612This guarrantees that, during an assignment or pass-by-value, the compiler copies
613every byte in our thread_id_t because the compiler guarrantees that the __size
614array, which we have ensured is the equal-largest element in the union, retains
615equivalence.
616
617This means that pthread_t values stored, assigned and passed by value will at least
618carry the value of any undefined padding bytes along and therefore ensure that
619those values remain consistent. Our comparisons will return consistent results and
620our hashes of [zero initialised] pthread_t values will also return consistent
621results.
622
623We have also removed the need for a pthread_null() function; we can initialise
624at declaration time or easily create our own const pthread_t to use in assignments
625later:
626
627const pthread_t null_tid = {0}; /* braces are required */
628
629pthread_t t;
630...
631t = null_tid;
632
633
634Note that we don't have to explicitly make use of the __size array at all. It's
635there just to force the compiler behaviour we want.
636
637
638Partial solutions without a pthread_normalize function
639
640
641An application-level pthread_null and pthread_compare proposal
642(and pthread_hash proposal by extention)
643
644In order to deal with the problem of scalar/aggregate pthread_t type disparity in
645portable code I suggest using an old-fashioned union, e.g.:
646
647Contraints:
648- there is no padding, or padding values are preserved through assignment and
649 pass-by-value (see above);
650- there are no extra non-id values in the pthread_t.
651
652
653Example 1: A null initialiser for pthread_t variables...
654
655typedef union {
656 unsigned char b[sizeof(pthread_t)];
657 pthread_t t;
658} init_t;
659
660const init_t initial = {0};
661
662pthread_t tid = initial.t; /* init tid to all zeroes */
663
664
665Example 2: A comparison function for pthread_t values
666
667typedef union {
668 unsigned char b[sizeof(pthread_t)];
669 pthread_t t;
670} pthcmp_t;
671
672int pthcmp(pthread_t left, pthread_t right)
673{
674 /*
675 * Compare two pthread handles in a way that imposes a repeatable but arbitrary
676 * ordering on them.
677 * I.e. given the same set of pthread_t handles the ordering should be the same
678 * each time but the order has no particular meaning other than that. E.g.
679 * the ordering does not imply the thread start sequence, or any other
680 * relationship between threads.
681 *
682 * Return values are:
683 * 1 : left is greater than right
684 * 0 : left is equal to right
685 * -1 : left is less than right
686 */
687 int i;
688 pthcmp_t L, R;
689 L.t = left;
690 R.t = right;
691 for (i = 0; i < sizeof(pthread_t); i++)
692 {
693 if (L.b[i] > R.b[i])
694 return 1;
695 else if (L.b[i] < R.b[i])
696 return -1;
697 }
698 return 0;
699}
700
701It has been pointed out that the C99 standard allows for the possibility that
702integer types also may include padding bits, which could invalidate the above
703method. This addition to C99 was specifically included after it was pointed
704out that there was one, presumably not particularly well known, architecture
705that included a padding bit in it's 32 bit integer type. See section 6.2.6.2
706of both the standard and the rationale, specifically the paragraph starting at
707line 16 on page 43 of the rationale.
708
709
710An aside
711
712Certain compilers, e.g. gcc and one of the IBM compilers, include a feature
713extention: provided the union contains a member of the same type as the
714object then the object may be cast to the union itself.
715
716We could use this feature to speed up the pthrcmp() function from example 2
717above by casting rather than assigning the pthread_t arguments to the union, e.g.:
718
719int pthcmp(pthread_t left, pthread_t right)
720{
721 /*
722 * Compare two pthread handles in a way that imposes a repeatable but arbitrary
723 * ordering on them.
724 * I.e. given the same set of pthread_t handles the ordering should be the same
725 * each time but the order has no particular meaning other than that. E.g.
726 * the ordering does not imply the thread start sequence, or any other
727 * relationship between threads.
728 *
729 * Return values are:
730 * 1 : left is greater than right
731 * 0 : left is equal to right
732 * -1 : left is less than right
733 */
734 int i;
735 for (i = 0; i < sizeof(pthread_t); i++)
736 {
737 if (((pthcmp_t)left).b[i] > ((pthcmp_t)right).b[i])
738 return 1;
739 else if (((pthcmp_t)left).b[i] < ((pthcmp_t)right).b[i])
740 return -1;
741 }
742 return 0;
743}
744
745
746Result thus far
747
748We can't remove undefined bits if they are there in pthread_t already, but we have
749attempted to render them inert for comparison and hashing functions by making them
750consistent through assignment, copy and pass-by-value.
751
752Note: Hashing pthread_t values requires that all pthread_t variables be initialised
753to the same value (usually all zeros) before being assigned a proper thread ID, i.e.
754to ensure that any padding bits are zero, or at least the same value for all
755pthread_t. Since all pthread_t values are generated by the library in the first
756instance this need not be an application-level operation.
757
758
759Conclusion
760
761I've attempted to resolve the multiple issues of type opacity and the possible
762presence of undefined bits and bytes in pthread_t values, which prevent
763applications from comparing or hashing pthread handles.
764
765Two complimentary partial solutions have been proposed, one an application-level
766scheme to handle both scalar and aggregate pthread_t types equally, plus a
767definition of pthread_t itself that neutralises padding bits and bytes by
768coercing semantics out of the compiler to eliminate variations in the values of
769padding bits.
770
771I have not provided any solution to the problem of handling extra values embedded
772in pthread_t, e.g. debugging or trap information that an implementation is entitled
773to include. Therefore none of this replaces the portability and flexibility of API
774functions but what functions are needed? The threads standard is unlikely to
775include that can be implemented by a combination of existing features and more
776generic functions (several references in the threads rationale suggest this.
777Therefore I propose that the following function could replace the several functions
778that have been suggested in conversations:
779
780pthread_t * pthread_normalize(pthread_t * handle);
781
782For most existing pthreads implementations this function, or macro, would reduce to
783a no-op with zero call overhead.
784
README.Watcom
1Watcom compiler notes
2=====================
3
4Status
5------
6Not yet usable. Although the library builds under Watcom it
7substantially fails the test suite.
8
9There is a working Wmakefile for wmake for the library build.
10
11invoke as any of:
12wmake -f Wmakefile clean WC
13wmake -f Wmakefile clean WC-inlined
14wmake -f Wmakefile clean WCE
15wmake -f Wmakefile clean WCE-inlined
16
17These build pthreadWC.dll and pthreadWCE.dll.
18
19There is a working Wmakefile for wmake for the test suite.
20
21invoke as any of:
22wmake -f Wmakefile clean WC
23wmake -f Wmakefile clean WCX
24wmake -f Wmakefile clean WCE
25wmake -f Wmakefile clean WC-bench
26wmake -f Wmakefile clean WCX-bench
27wmake -f Wmakefile clean WCE-bench
28
29
30Current known problems
31----------------------
32
33Library build:
34The Watcom compiler uses a different default call convention to MS C or GNU C and so
35applications are not compatible with pthreadVC.dll etc using pre 2003-10-14 versions
36of pthread.h, sched.h, or semaphore.h. The cdecl attribute can be used on exposed
37function prototypes to force compatibility with MS C built DLLs.
38
39However, there appear to be other incompatibilities. Errno.h, for example, defines
40different values for the standard C and POSIX errors to those defined by the MS C
41errno.h. It may be that references to Watcom's threads compatible 'errno' do set
42and return translated numbers consistently, but I have not verified this.
43
44Watcom defines errno as a dereferenced pointer returned by the function
45_get_errno_ptr(). This is similar to both the MS and GNU C environments for
46multithreaded use. However, the Watcom version appears to have a number of problems:
47
48- different threads return the same pointer value. Compare with the MS and GNU C
49versions which correctly return different values (since each thread must maintain
50a thread specific errno value).
51
52- an errno value set within the DLL appears as zero in the application even though
53both share the same thread.
54
55Therefore applications built using the Watcom compiler may need to use
56a Watcom built version of the library (pthreadWC.dll). If this is the case, then
57the cdecl function attribute should not be required.
58
59Application builds:
60The test suite fails with the Watcom compiler.
61
62Test semaphore1.c fails for pthreadWC.dll because errno returns 0 instead of EAGAIN.
63
README.WinCE
1WinCE port
2----------
3(See the file WinCE-PORT for a detailed explanation.)
4
5Make sure you define "WINCE" amongst your compiler flags (eg. -DWINCE).
6The config.h file will define all the necessary defines for you.
7