Section 4.9. Threads


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 Comparison

Solaris 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 Linux

Attribute

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 Comparison

Routine

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).




UNIX to Linux Porting. A Comprehensive Reference
UNIX to Linux Porting: A Comprehensive Reference
ISBN: 0131871099
EAN: 2147483647
Year: 2004
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net