Win32 Threading Primitives

This documents some of the design decisions made when implementing the C11 threading primitives on Windows

tss_t

Note: see C11 defect report: tss_t destruction unspecified for issues with the standard specification of tss_t. The PDCLib implementation at present follows that report.

Windows provides a thread local storage implementation which mostly implements the requirements of the standard. However, it doesn't implement destructors. To solve that, PDCLib uses two approaches

  • TLS callbacks: Executables and DLLs can specify "TLS callbacks", which are actions invoked in a similar manner to (but before) the traditional DllMain entry point for a DLL - and, importantly, are present in executables (which do not support DllMain). This works in all cases on NT 6.0 and later; but on older versions of Windows, these will only be invoked for the main executable and any DLLs loaded as direct dependencies of it (i.e. nothing loaded dynamically with LoadLibrary)
  • DllMain: This works for all libraries everywhere, but doesn't work for executables.

There are three cases to consider:

  • Linked statically from the executable: In this case we must use TLS callbacks
  • Linked statically from a DLL: In this case we must use DllMain for wide support
  • Linked dynamically: In this case we use our own DllMain

Our implementation strategy:

  • TBD

mtx_t

Our implementation doesn't use Win32 CRITICAL_SECTIONs; primarily because this would involve exposing <windows.h> to all people who include <threads.h>, or duplicating the definition of CRITICAL_SECTION (messy). Instead, we implement a locking primitive on top of Windows' event handles

  1. Every mutex has two members, _WaitEvHandle, which is a automatic reset Win32 event handle used for blocking, and _ThreadId, the ID of the current thread
  2. _ThreadId is initialized to 0
  3. To lock, mtx_lock calls InterlockedCompareExchange on _ThreadId. If the return value is 0, we successfully locked the mutex. If it is not 0, then we must block, so go ahead and block on the event handle
  4. To unlock, mtx_unlock sets _ThreadId to 0 and invokes SetEvent to unblock a thread