Thread implementations can be classified into two categories: user-level and kernel-level. The characteristic that distinguishes them is the granularity of scheduling provided by the host operating system. User-level threads are not individually scheduled by the operating system. Instead, each process implements its own scheduling algorithms to divide its CPU time among its threads. User-level threads can be difficult to utilize effectively because the most commonly used operating system API calls are synchronous. When a user-level thread blocks in a system call, the entire process is suspended and other threads in the process do not get the opportunity to run.
In contrast, kernel-level threads are (typically) individually scheduled by the operating system. When a kernel-level thread blocks in a system call, other threads in the process still receive their allocations of CPU time from the operating system. This makes it much simpler to write applications which use threads effectively. Many operating systems now incorporate kernel-level threads support.
The POSIX standard P1003.1c (also known as Pthreads) defines a standard API for access to thread support. However, this standard was developed during a period of strong market demand for thread support, and many OS vendors either implemented early drafts of this standard or implemented proprietary interfaces with the intention of later layering the standard interfaces on top of them. To further complicate matters, platforms such as Windows provide only proprietary interfaces even though much of the underlying function is compatible.
On platforms with kernel-level threads, the utility library is thread-safe. In other words, all utility library functions behave the same in a multi-threaded environment as in a single-threaded environment. Of course, this assumes that the application program avoids sharing individual data structures between threads or synchronizes operations on shared data structures. The utility library does enough internal synchronization to maintain its own integrity, uses thread-safe system calls where appropriate and uses thread-specific data for per-thread values such as the global SmartSockets error number.
In addition, the utility library includes cross-platform support for thread creation, synchronization, and thread-specific data. This common API makes it possible to write both applications and layered libraries that make use of threads and yet remain source-portable across all supported platforms. These functions are layered on top of the native OS support, which means that they are interoperable with direct use of the native system calls.
Multi-threaded applications must explicitly enable internal synchronization within the utility library with a call to TipcInitThreads. This allows single-threaded applications to avoid incurring unnecessary system call overhead. If any of the utility library’s thread support functions are used, an application should call TipcInitThreads before any other utility library function. The function TipcInitThreads is described in more detail in the TIBCO SmartSockets Application Programming Interface reference.
The utility library provides the T_THREAD opaque type to serve as a handle to threads created by the support functions. Thread creation requires a thread function that serves as the entry point for the thread in much the same way that the main function serves a program written in C. The thread function is defined:
The T_PTR argument to the function is passed through from the thread creation routine used, thus providing a mechanism for passing in arbitrary arguments. If the thread function returns, the returned T_PTR value becomes the exit code of the thread. Threads are created and manipulated with these functions:
The utility library provides support for three synchronization object types: mutexes, condition variables, and read/write mutexes (represented by the opaque types T_MUTEX, T_COND, and T_RW_MUTEX, respectively). Of the three, mutexes are the most primitive type. They provide the mechanism by which a thread can secure exclusive access to a shared resource using a simple locking metaphor. Only one thread may have a mutex locked at any given instant. The functions for creating and manipulating T_MUTEX objects are:
Condition variables provide the mechanism by which one thread can wait for another thread to complete an activity. Mutexes can only ensure serialization of an activity. T_COND instances are used in conjunction with mutexes, allowing a thread to wait until an arbitrary condition has occurred. The condition variable functions provided by the utility library are:
Read/write mutexes are useful for protecting a shared resource in scenarios where multiple threads need to examine the state of the resource concurrently, but exclusive access must still be available so that the state can be safely changed. T_RW_MUTEX objects rely upon both normal mutexes and condition variables for their implementation. The utility library functions for creating and working with read/write mutexes are:
Thread-specific data (TSD) provides a thread-safe substitute for global and file-scope variables. Rather than directly storing a value that is shared by several functions in a global variable, a TSD key (T_TSD_KEY) can be created. The value may then be placed in memory allocated from the heap and referenced by a unique T_PTR value for each thread. Each function that references the value must then use the TSD key to locate the shared value on the heap with the T_PTR value for the currently executing thread. Each TSD key may have an associated cleanup function, defined in this way:
Whenever a utility library thread exits, it iterates through all of its TSD values and calls any cleanup functions specified when the keys for those values were created. The functions for working with T_TSD_KEY objects and thread-specific data are:
TIBCO SmartSockets™ Utilities Software Release 6.8, July 2006 Copyright © TIBCO Software Inc. All rights reserved www.tibco.com |