4.9. Threads This section addresses issues that developers might face when migrating multithreaded applications from Solaris to Linux. On the Linux side, the material in this section is based on the new implementation of a POSIX thread library (the Native POSIX Thread Library, NPTL for short, developed by Ulrich Drepper). We have provided an introduction to NPTL in Chapter 3. Here we focus on the differences between threading implementations in Solaris and Linux. 4.9.1. Solaris libthread Versus Linux libpthread Solaris supports two threading implementations: Solaris threads and POSIX threads (pthreads). There are a few functions implemented by the Solaris threads API but not by pthreads API and vice versa. For those functions that do match, the associated arguments might not. The following features are exclusively supported by the Solaris threads API: The ability to create daemon threads. The process exits when all of its nondaemon threads exit. Daemon threads do not affect its process exit status and are ignored when counting the number of thread exits. The ability to suspend or continue the execution of the thread via tHR_suspend() and thr_continue(). Note that tHR_suspend() suspends the target thread with no regard to the locks that the thread might be holding. This will result in deadlock if the suspending thread calls a function that requires a lock held by the suspended target thread. The ability to allow a thread to wait for any undetached thread in the process to terminate. This is achieved when the first argument of tHR_join() is set to 0. If we set the first argument of pthread_join() to 0, the program will be terminated with a segmentation fault. Table 4-13 compares the Solaris threads APIs with the Linux NPTL APIs. Table 4-13. Solaris Threads and Linux POSIX pthreads ComparisonSolaris Threads API | Linux POSIX Threads API | Description |
---|
thr_create() | pthread_create() | Creates a new thread of control. | thr_exit() | pthread_exit() | Terminates the execution of the calling thread. | tHR_join() | pthread_join() | Suspends the calling thread until the target thread completes. | thr_kill() | pthread_kill() | Sends a signal to another thread. | thr_self() | pthread_self() | Returns the thread ID of the calling process. | thr_yield() | sched_yield() | Makes the current thread yield to another thread. | tHR_getprio() | pthread_getschedparam() | Retrieves a thread's priority parameters. | thr_setprio() | pthread_setschedparam() | Modifies a thread's priority parameters. | thr_getspecific() | pthread_getspecific() | Binds a new thread-specific value to the key. | thr_setspecific() | pthread_setspecific() | Binds a new thread-specific value to the key. | tHR_getconcurrency() | pthread_getconcurrency() | Gets thread concurrency level. | thr_setconcurrency() | pthread_setconcurrency() | Sets thread concurrency level. | thr_sigsetmask() | pthread_sigmask() | Changes or examines the calling thread's signal mask. | thr_keycreate() | pthread_key_create() | Creates a key that locates data specific to a thread. | N/A | pthread_key_delete() | Deletes a key that locates data specific to a thread. | thr_suspend() | N/A | Suspends the execution of the specified thread. | thr_continue() | N/A | Resumes the execution of a suspended thread. | mutex_lock() | pthread_mutex_lock() | Locks the given mutex. | mutex_unlock() | pthread_mutex_unlock() | Unlocks the given mutex. | mutex_trylock() | pthread_mutex_trylock() | The calling thread not blocked if the mutex is already locked but returns with the error code EBUSY. | mutex_init() | pthread_mutex_init() | Initializes the mutex. | mutex_destroy() | pthread_mutex_destroy() | Destroys a mutex object. | cond_wait() | pthread_cond_wait() | Blocks on the conditional variable. | cond_timedwait() | pthread_cond_timedwait() | Blocks on the conditional variable until the specified absolute time passes. | cond_reltimedwait() | N/A | Blocks on the conditional variable until the specified relative time passes. | cond_signal() | pthread_cond_signal() | Signals a condition. | cond_broadcast() | pthread_cond_braodcast() | Broadcasts a condition. | cond_init() | pthread_cond_init() | Initializes condition variables. | cond_destroy() | pthread_cond_destroy() | Destroys condition variables. | rwlock_init() | pthread_rwlock_init() | Initializes a read-write lock object. | rwlock_destroy | pthread_rwlock_destroy() | Destroys a read-write lock object. | rw_rdlock() | pthread_rwlock_rdlock() | Locks a read-write lock object for reading. | rw_wrlock() | pthread_rwlock_wrlock() | Locks a read-write lock object for writing. | rw_unlock() | pthread_rwlock_unlock() | Unlocks a read-write lock object. | rw_tryrdlock() | pthread_rwlock_tryrdlock() | Tries to lock a read-write lock object for reading. | rw_trywrlock() | pthread_rwlock_ trywrlock() | Tries to lock a read-write lock object for writing. | sema_init() | sem_init() | Initializes a semaphore. | sema_destroy() | sem_destroy() | Destroys a semaphore. | sema_wait() | sem_wait() | Blocks until the semaphore count is greater than 0 and decrements the count. | sema_post() | sem_post() | Atomically increments the semaphore. | sema_trywait() | sem_trywait() | Atomically decrements the semaphore. |
The behavior of fork() in Solaris 9 and earlier releases is different from fork() in POSIX threads. In POSIX threads, fork() creates a new process, duplicating the complete address space in the child. However, it duplicates only the calling thread in the child process. The Solaris threads API also provides the replicate all fork semantics, forkall(). This function duplicates the address space and all the threads in the child. This feature is not supported by the POSIX thread standard. 4.9.2. Solaris libpthread Versus Linux libpthread Although Solaris and Linux both implement the POSIX 1003.1, there are minor differences in platform implementations. In this section, we focus on these differences. Table 4-14 shows the default values of attributes for POSIX threads on Linux compared to those on Solaris. Table 4-14. Default pthreads Attributes for Solaris and LinuxAttribute | Solaris Default Value | Linux Default Value | Description of Default |
---|
Contentionscope | PTHREAD_SCOPE_ PROCESS | PTHREAD_SCOPE_ SYSTEM | Threads contend for CPU time with all processes running on the machine. | Detachstate | PTHREAD_CREATE_JOINABLE | PTHREAD_CREATE_JOINABLE | Thread created in the joinable state. | Stacksize | NULL[18] | Variable[19] | Minimum stack size (in bytes). | Priority | 0 | 0 | Priority of the thread. | Policy | SCHED_OTHER | SCHED_OTHER | The regular scheduling policy. | Inheritsched | PTHREAD_EXPLICIT_SCHED | PTHREAD_INHERIT_SCHED | Indicates how scheduling policy and parameters are determined for the newly created thread. | Guardsize | PAGESIZE | PAGESIZE | Size of guard area for a thread's created stack. |
[18] 1MB for 32-bit processes and 2MB for 64-bit processes
[19] Dependent on the distribution: 4194304 bytes in SuSE SLES9 and 10485760 bytes in RHEL4 Table 4-15 compares the Solaris pthreads APIs with the Linux NPTL APIs. Table 4-15. Solaris pthread and Linux pthread ComparisonRoutine | Solaris | Linux |
---|
pthread_atfork | Yes | Yes[20] | pthread_attr_destroy | Yes | Yes | pthread_attr_getdetachstate | Yes | Yes | pthread_attr_getguardsize | Yes | Yes | pthread_attr_getinheritsched | Yes | Yes | pthread_attr_getschedparam | Yes | Yes | pthread_attr_getschedpolicy | Yes | Yes | pthread_attr_getscope | Yes | Yes | pthread_attr_getstack | Yes | Yes | pthread_attr_getstackaddr | Yes | Yes | pthread_attr_getstacksize | Yes | Yes | pthread_attr_init | Yes | Yes | pthread_attr_getaffinity_np | No | Yes | pthread_attr_setaffinity_np | No | Yes | pthread_attr_setdetachstate | Yes | Yes | pthread_attr_setguardsize | Yes | Yes | pthread_attr_setinheritsched | Yes | Yes | pthread_attr_setschedparam | Yes | Yes | pthread_attr_setschedpolicy | Yes | Yes | pthread_attr_setscope | Yes | Yes | pthread_attr_setstack | Yes | Yes | pthread_attr_setstackaddr | Yes | Yes | pthread_attr_setstacksize | Yes | Yes | pthread_barrierattr_destroy | Yes | Yes | pthread_barrierattr_getpshared | Yes | Yes | pthread_barrierattr_init | Yes | Yes | pthread_barrierattr_setpshared | Yes | Yes | pthread_barrier_destroy | Yes | Yes | pthread_barrier_init | Yes | Yes | pthread_barrier_wait | Yes | Yes | pthread_cancel | Yes | Yes | pthread_cleanup_pop | Yes | Yes | pthread_cleanup_pop_restore_np | No | Yes | pthread_cleanup_push | Yes | Yes | pthread_cleanup_push_defer_np | No | Yes | pthread_condattr_destroy | Yes | Yes | pthread_condattr_getclock | Yes | Yes | pthread_condattr_getpshared | Yes | Yes | pthread_condattr_init | Yes | Yes | pthread_condattr_setclock | Yes | Yes | pthread_condattr_setpshared | Yes | Yes | pthread_cond_broadcast | Yes | Yes | pthread_cond_destroy | Yes | Yes | pthread_cond_init | Yes | Yes | pthread_cond_reltimedwait_np | Yes | No | pthread_cond_signal | Yes | Yes | pthread_cond_timedwait | Yes | Yes | pthread_cond_wait | Yes | Yes | pthread_create | Yes | Yes | pthread_detach | Yes | Yes | pthread_equal | Yes | Yes | pthread_exit | Yes | Yes | pthread_getattr_np | No | Yes | pthread_getconcurrency | Yes | Yes | pthread_getschedparam | Yes | Yes | pthread_getspecific | Yes | Yes | pthread_join | Yes | Yes | pthread_key_create | Yes | Yes | pthread_key_delete | Yes | Yes | pthread_kill | Yes | Yes | pthread_kill_other_threads_np | No | Yes[21] | pthread_mutexattr_destroy | Yes | Yes | pthread_mutexattr_getprioceiling | Yes | No | pthread_mutexattr_getprotocol | Yes | No | pthread_mutexattr_getpshared | Yes | Yes | pthread_mutexattr_getrobust_np | Yes | No | pthread_mutexattr_gettype | Yes | Yes | pthread_mutexattr_init | Yes | Yes | pthread_mutexattr_setprioceiling | Yes | No | pthread_mutexattr_setprotocol | Yes | No | pthread_mutexattr_setpshared | Yes | Yes | pthread_mutexattr_setrobust_np | Yes | No | pthread_mutexattr_settype | Yes | Yes | pthread_mutex_consistent_np | Yes | No | pthread_mutex_destroy | Yes | Yes | pthread_mutex_getprioceiling | Yes | No | pthread_mutex_init | Yes | Yes | pthread_mutex_lock | Yes | Yes | pthread_mutex_reltimedlock_np | Yes | No | pthread_mutex_setprioceiling | Yes | No | pthread_mutex_timedlock | Yes | Yes | pthread_mutex_trylock | Yes | Yes | pthread_mutex_unlock | Yes | Yes | pthread_once | Yes | Yes | pthread_rwlockattr_destroy | Yes | Yes | pthread_rwlockattr_getkind_np | No | Yes | pthread_rwlockattr_getpshared | Yes | Yes | pthread_rwlockattr_init | Yes | Yes | pthread_rwlockattr_setkind_np | No | Yes | pthread_rwlockattr_setpshared | Yes | Yes | pthread_rwlock_destroy | Yes | Yes | pthread_rwlock_init | Yes | Yes | pthread_rwlock_rdlock | Yes | Yes | pthread_rwlock_reltimedrdlock_np | Yes | No | pthread_rwlock_reltimedwrlock_np | Yes | No | pthread_rwlock_timedrdlock | Yes | Yes | pthread_rwlock_timedwrlock | Yes | Yes | pthread_rwlock_tryrdlock | Yes | Yes | pthread_rwlock_trywrlock | Yes | Yes | pthread_rwlock_unlock | Yes | Yes | pthread_rwlock_wrlock | Yes | Yes | pthread_self | Yes | Yes | pthread_setcancelstate | Yes | Yes | pthread_setcanceltype | Yes | Yes | pthread_setconcurrency | Yes | Yes | pthread_setschedparam | Yes | Yes | pthread_setschedprio | Yes | No | pthread_setspecific | Yes | Yes | pthread_sigmask | Yes | Yes | pthread_spin_destroy | Yes | Yes | pthread_spin_init | Yes | Yes | pthread_spin_lock | Yes | Yes | pthread_spin_trylock | Yes | Yes | pthread_spin_unlock | Yes | Yes | pthread_testcancel | Yes | Yes | pthread_timedjoin_np | No | Yes | pthread_tryjoin_np | No | Yes |
[20] In NPTL, thread handlers registered with pthread_atfork() are not run if vfork is used.
[21] This is a nonportable LinuxThreads extension. It is no longer needed and is not available in the NPTL. Although many applications that run on the Linux 2.4 kernel will migrate to the Linux 2.6 kernel without recompilation, the addition of NPTL may require minor modifications in multithreaded applications. Instructions to migrate applications from LinuxThreads to NPTL are beyond the scope of this chapter. Additional information about migrating to use NPTL on the 2.6 kernel is available at http://linuxdevices.com/articles/AT6753699732.html. 4.9.3. Cancellation Points Thread cancellation allows a thread to terminate the execution of any thread in the process. A cancellation point is a certain point defined by the system at which cancellation can occur. When a thread changes the system or program state just before a cancellation point, and should restore that state before the thread is canceled, you can place a cleanup handler before the cancellation point with pthread_cleanup_push. Wherever a thread restores the changed state, you can pop the cleanup handler from the cleanup stack with pthread_cleanup_pop(3C). Both Solaris and Linux NPTL threads offer the range of cancellation points specified in IEEE POSIX 1003.1-2001 and the Single UNIX Specification. 4.9.4. C++ Exceptions and Linux Threads Searching the Internet for related topics on C++ exceptions and POSIX threads yields a variety of opinions on how C++ should handle POSIX thread cancellations or vice versa. The fact is that the POSIX thread programming model does not take C++ into account (it is based on C), and the C++ language standard only recently has begun to specify behavior in this area. The sample programs in Example 4-2 involve simple C++ routines mixed within POSIX threads. Compiling and running in both Solaris using the Sun Studio10 C++ compiler and SuSE 9.1 Linux with g++ version 3.3.3 yielded different results. Example 4-2. Exception_1.c #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> class FooClass { public: FooClass() {} ~FooClass() { pthread_cancel(pthread_self()); pthread_testcancel(); } }; void* thread_func(void*) { try { FooClass cid; throw 1; } catch (int&) {} return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); pthread_join(tid, NULL); return 0; } Compiling and running exception _1.c on Linux yields the following: $ g++ exception_1.cc -o exception_1 -lpthread -D_REENTRANT $ ./exception_1 Aborted (core dumped) Compiling and running exception _1.c on Solaris yields the following: $ CC exception_1.cc -o exception_1 -lpthread -D_REENTRANT -g $ ./exception_1 No core dumped In the preceding case, the pthread_cancel within the destructor of FooClass is considered an exception. The book The C++ Programming Language, 3rd Edition, by Bjarne Stroustrup (pp. 373, 378) mentions a rule about exceptions in destructorsjust don't do it! Dr. Stroustrup explains this in the following manner: During stack unwinding, the exception-handling mechanism exits a scope containing an object with a destructor. In this case, an exception may not escape from the destructor itself. If it does, it is considered a failure in the exception handling mechanism and std::terminate is called. Dr. Stroustrup offers a solution. See the following code snippet to catch the exception (but that solution did not work in our case): ~FooClass() { try { pthread_cancel(pthread_self()); pthread_testcancel(); } catch (…) { // do whatever throw; } } One solution to try and close the cancellation hole within destructors is to disable cancellation, as in the following code snippet: ~FooClass() { int oldstate; // Disable cancellation at entry pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); pthread_cancel(pthread_self()); pthread_testcancel(); // Enable cancellation before exiting pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate); } Example 4-3 involves simple throwing of exceptions within threads. | Example 4-3. Exception_2.cc #include <iostream> #include <cstdio> #include <cstdlib> #include <pthread.h> #include <unistd.h> using namespace std; void * thread_func(void *); int main(int argc, char *argv[]) { pthread_t t; int i; int status; try { if( pthread_create(&t,NULL,thread_func,(void *)i) != 0) { cerr << "pthread_create failed" << endl; return 1; } pthread_join(t, NULL); cout << "end of thread " << endl; } catch(...) { cout << "unexpected exception" << endl; } } void * thread_func(void *arg) { int i; for (i=0; i<4; i++) { sleep(1); try { cout << "throwing exception" << endl; throw "exception"; } catch(char *st) { cout << "thread_func caught" << st << endl; } } return 0; } | Compiling and running exception_2.cc on Linux yields the following: $ g++ exception_2.cc -o exception_2 -lpthread -D_REENTRANT $ ./exception_2 throwing check Aborted (core dumped) Compiling and running exception_2.cc on Solaris yields the following: $ CC exception_2.cc -o exception_2 -lpthread -D_REENTRANT -g $ ./exception_2 throwing exception Abort (core dumped) Changing thread_func to throw an integer instead of a char * changes the behavior of the program to complete its run without aborting:[22] [22] Note that the sample exception_2.cc compiled and ran fine on AIX 5.3 using xlC version 6 compiler. void * thread_func(void *arg) { int i; for (i=0; i<4; i++) { sleep(1); try { cout << "throwing exception" << endl; throw 1; } catch(int&) { cout << "thread_func caught" << endl; } } return 0; } Another change to make this work is to put a "catchall" catcher: void * thread_func(void *arg) { int i; for (i=0; i<4; i++) { sleep(1); try { cout << "throwing exception" << endl; throw 1; } catch(…) { cout << "thread_func caught" << endl; } } return 0; } In the case of program listing exception_2.cc, the problem seems to be that of uncaught exceptions. Again referring to The C++ Programming Language, 3rd Edition, by Bjarne Stroustrup (p. 381), Dr. Stroustrup explains that if an exception is thrown but not caught, the function std::terminate is called. Catching the exception prevents the core dump. The preceding programs clearly show that C++ exceptions and POSIX threads do not mix well. The consensus from porting experience[23] indicates that there is no well-defined standard as to how C++ needs to handle pthread_cancellations and exceptions, especially within destructors.[24] The one piece of advice for porting engineers is to be aware of these differences and make sure to test error paths and cleanup handlers as thoroughly as the good path during the testing phase of the porting project. [23] Even comments from the comp.programming.threads newsgroup
[24] ANSI C++ does not recommend throwing exceptions in the constructor and destructor of a class whose instances may be defined globally (static-globally). |