Public Domain C Library Project
The final release of the C11 international standard leaves many aspects regarding thread local storage unspecified. Specifically, the following aspects are unspecified:
TSS_DTOR_ITERATIONS
constant)This ambiguity leaves the utility of the TSS feature in a fully conforming application (i.e. one not relying on additional assertions by the implementation) greatly reduced. In particular, it prevents the usage of the thread specific storage feature for reliable resource cleanup
This proposal is submitted in relation to specification defect reports DR 416 (by the same author as this proposal) and DR 424. This proposal will look at existing implementations of thread specific storage and their behavior, and will then propose alterations to the C11 standard .
This proposal will look at the specifications of thread specific storage and related mechanisms under two common platforms (POSIX.1 2008 and Microsoft Windows) and additionally at the defined behaviour of the C++11 international standard.
POSIX.1 implements thread specific storage under the POSIX Threads ("pthreads") API. It is implemented in terms of the type pthread_key_t
, which mirrors tss_t
, and four functions, which exactly mirror those provided by the C11 standard:
C11 Function | POSIX.1 Function |
---|---|
tss_create | pthread_key_create |
tss_get | pthread_getspecific |
tss_set | pthread_setspecific |
tss_delete | pthread_key_delete |
In addition, POSIX.1 defines the constant PTHREAD_DESTRUCTOR_ITERATIONS
which has a description which presumably matches the intent of the C11 specification's TSS_DTOR_ITERATIONS
constant.
POSIX.1 defines that, at thread exit time
NULL
and the key's destructor will be invoked with the value that the key had immediately prior to being set to NULL
PTHREAD_DESTRUCTOR_ITERATIONS
times.pthread_exit
(the function which exits a thread, analogously to C11's thrd_exit
) from within a destructorPOSIX.1 defines that pthread_key_delete
and exit
do not cause destructor invocations.
POSIX.1 leaves undefined whether destructors for keys created or destroyed concurrently with a thread running destructors (in another thread, or from a destructor running on the thread executing destructors) will alter the set of destructors run by said thread.
Thread Specific Storage on Microsoft Windows is implemented in terms of four functions:
// Common Win32 API types, defined for those unfamiliar: typedef uint32_t DWORD; typedef void *LPVOID; // Win32 TLS functions DWORD TlsAlloc(void); BOOL TlsFree(DWORD dwTlsIndex); BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue); LPVOID TlsGetValue(DWORD dwTlsIndex); |
The function TlsAlloc
is used in order to allocate a new thread specific storage "index.", Note that the Windows API does not directly have any concept of a thread local storage destructor. The TlsFree
function is used to deallocate a thread specific storage index. The TlsSetValue
and TlsGetValue
functions, aside from the differences in the types involved and method of indicating errors, behave in the same manner as the tss_set
and tss_get
functions in the C11 standard.
Windows does not directly provide destructor support for thread specific storage objects, but does provide mechanisms by which they may be implemented:
DllMain
, which receives notifications on various events, including thread startup and terminationEither method may be used to implement thread local storage destructors (the former is used by, for example, the pthreads-win32 library to implement the POSIX.1 threading library on top of Windows).
A caveat which must be noted with either of the above methods of implementing thread specific storage destructors is that in both cases the notifications are delivered while the system holds a lock on an internal mutex (The "Loader lock", which is also taken internally during calls to certain functions exposed by the system)
C++2011 introduces the language keyword thread_local
, aligned to the thread_local
macro introduced by <threads.h>
and _Thread_local
keyword in C11
, which introduces an object of thread local storage duration. C++ objects have both constructors and destructors, and therefore must be allocated and constructed before first use in a thread, and destroyed and deallocated at thread exit.
C++ defines that thread local storage destructors are called
exit
for objects associated with the thread that invoked exit
, prior to commencing invocation of functions registered with atexit
main
, for the objects associated with the process' initial thread (following the rule that an application which returns from main
shall behave as if exit
was invoked with the value returned)It is noted that the requirement that destructors be called from exit
differs from that of POSIX.1. It is also noted that C++ does not need to invoke destructors multiple times because of the nature of C++ objects.
The proposed behavior is to align the C specification behavior with POSIX.1
The behavior of the thread specific storage primitives defined in the POSIX.1 specification are thought to be possible to implement on all platforms which support threads. On platforms which do not implement thread specific storage destructors natively, or which do so in a manner incompatible with the mechanisms defined by POSIX.1 can be handled either by
pthreads-win32
, referenced aboveAn example implementation would be for the C library to invoke destructors manually from within the thrd_exit
function. This would be sufficient to support fully conforming C programs, though may not be useful for applications where some components may not use the C library threading primitives.
This proposed corrigendum is a lightly edited version of that originally proposed in DR 416:
After 7.26.5.1p2, add
Returning from func
shall have the same behaviour as invoking thrd_exit
with the returned value
Change 7.26.5.5 part 2 from
The thrd_exit
function terminates execution of the calling thread and sets its result code to res
.
to
For every thread specific storage key which was created with a non-NULL destructor and for which the value is non-NULL, thrd_exit
shall set the value associated with the key to NULL and then invoke the destructor with its previous value. The order in which destructors are invoked is unspecified.
If after this process there remain keys with both non-NULL destructors and values, the implementation shall repeat this process up to TSS_DTOR_ITERATIONS
times.
Following this, the thrd_exit
function terminates execution of the calling thread and sets its result code to res
.
After 7.26.6.1p2, add
The value NULL shall be associated with the newly created key in all existing threads. Upon thread creation, the value associated with all keys shall be initialized to NULL
Note that destructors associated with thread specific storage are not invoked at process exit.
It is undefined to call tss_create
from within a destructor invocation.
It is undefined if calls to tss_set,
tss_get
or tss_delete
for a storage are valid on a thread if the tss_create
call which allocated it completed after the thread commenced executing destructors.
To 7.26.6.2p2, append
If tss_delete
is called while another thread is executing destructors, whether this will affect the number of invocations of the destructor associated with key
on that thread is unspecified. If the thread from which tss_delete
is invoked is executing destructors, then no further invocations of the destructor associated with key
will occur on said thread.
Calling tss_delete
will not result in the invocation of any destructors.
After 7.26.6.4p2, add
This action will not invoke the destructor associated with the key on the value being replaced.
(This additionally clarifies whether or not a destructor will be invoked for a storage created after a thread has already begun executing destructors: because tss_set
is an undefined operation, a value may never be associated with the storage and therefore the destructor may never be invoked)
Changes since DR 416:
tss_create
from within a destructor invocation is undefinedtss_set
, tss_get
or tss_delete
on a storage created after a thread has begun executing destructors from within that thread is undefined
|