In DIGITAL UNIX Version 4.0D, Thread Local Storage (TLS) support has been added to the DEC C compiler.
Thread Local Storage is a name for data that has static extent (that is, not on the stack) for the lifetime of a thread in a multithreaded process, and whose allocation is specific to each thread.
In standard multithreaded programs, static extent data is shared among all threads of a given process, whereas Thread Local Storage is allocated on a per-thread basis such that each thread has its own copy of the data that can be modified by that thread without affecting the value seen by the other threads in the process. For a complete discussion of threads, see the Guide to DECthreads and the Programmer's Guide.
The essential functionality of Thread Local Storage is and has been
provided by explicit application program interfaces (APIs) such as
POSIX (DECThreads)
pthread_key_create()
,
pthread_setspecific()
,
pthread_getspecific()
,
and
pthread_key_delete()
.
Although these APIs are portable to POSIX-conforming platforms, using
them can be cumbersome and error-prone. Significant engineering
work is typically required to take existing single-threaded code and
make it thread-safe by replacing all of the appropriate static and
external variable declarations and their uses by calls to these
thread-local APIs. Furthermore, for Win32 platforms there is
a somewhat different set of APIs:
TlsAlloc()
,
TlsGetValue()
,
TlsSetValue()
,
and
TlsFree()
,
which have the same kinds of usability problems as the POSIX APIs.
By contrast, the Thread Local Storage language feature is much simpler to use than any of the APIs, and it is especially convenient to use when converting single-threaded code to be multithreaded. This is because the change to make a static or external variable have a thread-specific value involves only adding a storage-class qualifier to its declaration. The compiler, linker, program loader, and debugger effectively implement the complexity of the API calls automatically for variables declared with this qualifier. Unlike coding to the APIs, you do not need to find and modify all uses of the variables, or to add explicit allocation and deallocation code. While the language feature is not generally portable under any formal programming standard, it is portable between DIGITAL UNIX and Win32 platforms.
The C and C++ compilers for DIGITAL UNIX include the extended
storage-class attribute,
__thread
.
You must use the
__thread
attribute with the
__declspec
keyword to declare a thread variable. For example, the following code
declares an integer thread local variable and initializes it with a
value:
__declspec( __thread ) int tls_i = 1;
You must observe the following guidelines and restrictions when declaring thread local objects and variables:
__thread
storage-class attribute only to data declarations and definitions.
You cannot use it on function declarations or definitions. For
example, the following code generates a compiler error:
#define Thread __declspec( __thread ) Thread void func(); // Error
__thread
attribute only on data items with static storage duration. This
includes global data objects (both static and external), local static
objects, and static data members of C++ classes. You cannot declare
automatic or register data objects with the
__thread
attribute. For example, the following code generates compiler errors:
#define Thread __declspec( __thread ) void func1() { Thread int tls_i; // Error }
int func2( Thread int tls_i ) // Error { return tls_i; }
__thread
attribute for the declaration and the definition of a thread-local
object, whether the declaration and definition occur in the same file
or separate files. For example, the following code generates an error:
#define Thread __declspec( __thread ) extern int tls_i; // This generates an error, because the int Thread tls_i; // declaration and the definition differ.
__thread
attribute as a type modifier. For example, the following code
generates a compile-time error:
char __declspec( __thread ) *ch; // Error
#define Thread __declspec( __thread ) Thread int tls_i; int *p = &tls_i; // ERROR
Standard C permits initialization of an object or variable with an expression involving a reference to itself, but only for objects of nonstatic extent. Although C++ normally permits such dynamic initialization of an object with an expression involving a reference to itself, this type of initialization is not permitted with thread local objects. For example:
#define Thread __declspec( __thread ) Thread int tls_i = tls_i; // C and C++ error int j = j; // Okay in C++; C error Thread int tls_i = sizeof( tls_i ) // Okay in C and C++
Note that a
sizeof
expression that includes the object being initialized does not
constitute a reference to itself and is allowed in C and C++.