Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

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 loosely inspired by Linux' futexes. Ignoring support for recursive mutexes, our mutex implementation works as follows:

  1. Every mutex has two members, _WaitEvHandle, which is a automatic reset Win32 event handle used for blocking, and _State, which is a 32-bit integer
  2. _State is initialized to -1
  3. To lock, mtx_lock calls InterlockedIncrement on _State. 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 calls InterlockedDecrement on _State. If the return value is -1, nobody was waiting and we can return without touching the kernel; otherwise, we must SetEvent the event handle in order to unblock a thread
  5. Note that at no point once someone has blocked on the mutex will the _State member return to -1 until all threads blocked on the mutex 

Waits with timeouts

Timed waits must be careful about lowering the _State variable after their wait times out. In particular, if after the timeout _State == 0, then they actually just got granted the mutex - and, more importantly - the event object is set. When this occurs, they must do a wait with a timeout of zero on the event object and repeat this every time the wait times out until _State no longer equals 0 (which indicates somebody else took the mutex) or until the wait completes (indicating that they got the mutex)

  • No labels