Section 6.10. Threads


6.10. Threads

This section addresses issues that developers might face when migrating multithreaded applications from HP-UX 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.

The Linux threading library in all the released versions of Linux prior to 2.6 is known as LinuxThreads. This library has been supported by the GNU C library since glibc 2.0 and is largely POSIX-compliant. Starting in the 2.6 kernel, NPTL was introduced. It provides a significant performance improvement over LinuxThreads. NPTL is also far more compliant with the POSIX specification than the LinuxThreads package was. However, just using the 2.6 kernel does not mean that the NPTL is being used. All modern Linux distributions ship NPTL as the default threads package, and many ship both NPTL and LinuxThreads. However, it is recommended that all new applications to be ported to the Linux 2.6 platform use NPTL as the threading library.

Because NPTL is relatively new, it would only be natural for different Linux distributions to support older versions of the LinuxThreads library[31] for backward compatibility. To determine which threading library a system uses, execute the getconf command (part of the glibc package) to examine the GNU_LIBPTHREAD_VERSION environment variable, as in the following command example:

[31] LinuxThreads is another library that implements POSIX 1003.1c API specifications.

$ getconf GNU_LIBPTHREAD_VERSION linuxthreads-0.10             This was returned from an old                               Red Hat installation. 


If your system uses the NPTL, the command returns the value of NPTL that your system is using, as in the following example:

$ getconf GNU_LIBPTHREAD_VERSION NPTL 0.61                     This was returned from                               SUSE 9.1 installation. $ getconf GNU_LIBPTHREAD_VERSION NPTL 2.3.3                    This was returned from                               Fedora 2.6.9-1.667                               installation. 


To tell which compiler suite the Linux distribution was compiled and linked with, use the following method.

To find the libpthreads library linked with /bin/ls:

$ ldd /bin/ls | grep libc.so.6     libc.so.6 => /lib/tls/i586/libc.so.6 (0x00b9c000) $ /lib/tls/i586/libc.so.6 GNU C Library stable release version 2.3.3, by Roland McGrath et al. Copyright (C) 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 3.4.2 20041017 (Red Hat 3.4.2-6.fc3). Compiled on a Linux 2.4.20 system on 2004-10-27. Available extensions:     GNU libio by Per Bothner     crypt add-on version 2.1 by Michael Glad and others     Native POSIX Threads Library by Ulrich Drepper et al     The C stubs add-on version 2.1.2.     BIND-8.2.3-T5B     NIS(YP)/NIS+ NSS modules 0.19 by Thorsten Kukuk     Glibc-2.0 compatibility add-on by Cristian Gafton     GNU Libidn by Simon Josefsson Thread-local storage support included. For bug reporting instructions, please see: <http://www.gnu.org/software/libc/bugs.html>. 


As the preceding output shows, the libc.so.6 is linked with the Native POSIX Threads Library by Ulrich Drepper.

6.10.1. Threads Support in HP-UX and Linux

HP-UX announced formal support for POSIX 1003.1 threads and discontinued support for new applications written to use DCE threads in version 11 of its HP-UX operating system. Although support for DCE threads has been discontinued, applications written to these standards still exist. Porting these applications to Linux requires them to adhere to the new POSIX 1003.1 standards. Table 6-15 compares DCE threads and POSIX threads on both HP-UX and Linux.

Table 6-15. Linux and HP-UX Threads Implementation Comparison

HP-UX DCE

HP-UX POSIX

Linux NPTL

atfork

pthread_atfork

pthread_atfork

hp__pthread_equal

None

None

pthread__cancel_thread

pthread_cancel

pthread_cancel

pthread_attr_create

pthread_attr_init

pthread_attr_init

pthread_attr_delete

pthread_attr_destroy

pthread_attr_destroy

pthread_attr_

pthread_attr_

pthread_attr_

getguardsize_np

getguardsize

getguardsize

pthread_attr_

pthread_attr_

pthread_attr_

getinheritsched

getinheritsched

getinheritsched

pthread_attr_getprio

pthread_attr_

pthread_attr_

 

getschedparam

getschedparam

pthread_attr_getsched

pthread_attr_

pthread_attr_

 

getschedpolicy

getschedpolicy

pthread_attr_

pthread_attr_

pthread_attr_

getstacksize

getstacksize

getstacksize

pthread_attr_

pthread_attr_

pthread_attr_

setguardsize_np

setguardsize

setguardsize

pthread_attr_

pthread_attr_

pthread_attr_

setinheritsched

setinheritsched

setinheritsched

pthread_attr_setprio

pthread_attr_

pthread_attr_

 

setschedparam

setschedparam

pthread_attr_setsched

pthread_attr_

pthread_attr_

 

setschedpolicy

setschedpolicy

pthread_attr_

pthread_attr_

pthread_attr_

setstacksize

setstacksize

setstacksize

pthread_cancel

pthread_cancel

pthrhead_cancel

pthread_cond_broadcast

pthread_cond_broadcast

pthread_cond_broadcast

pthread_cond_destroy

pthread_cond_destroy

pthread_cond_destroy

pthread_cond_init

pthread_cond_init

pthread_cond_init

pthread_cond_signal

pthread_cond_signal

pthread_cond_signal

pthread_cond_signal_

None

None

int_np

  

pthread_cond_timedwait

pthread_cond_timedwait

pthread_cond_timedwait

pthread_cond_wait

pthread_cond_wait

pthread_cond_wait

pthread_condattr_create

pthread_condattr_init

pthread_condattr_init

pthread_condattr_

pthread_condattr_

pthread_condattr_

delete

destroy

destroy

pthread_create

pthread_create

pthread_create

pthread_ctxcb_hpux

None

None

pthread_delay_np

nanosleep

nanosleep

pthread_detach

pthread_detach

pthread_detach

pthread_equal

pthread_equal

pthread_equal

pthread_exit

pthread_exit

pthread_exit

pthread_get_

get_expiration_time

get_expiration_time

expiration_np

  

pthread_getprio

pthread_getschedparam

pthread_getschedparam

pthread_getscheduler

pthread_getschedparam

pthread_getschedparam

pthread_getspecific

pthread_getspecific

pthread_getspecific

pthread_is_

None

None

multithreaded_np

  

pthread_join

pthread_join

pthread_join

pthread_keycreate

pthread_key_create

pthread_key_create

pthread_lock_
global_np

None

None

pthread_mutex_destroy

pthread_mutex_destroy

pthread_mutex_destroy

pthread_mutex_init

pthread_mutex_init

pthread_mutex_init

pthread_mutex_lock

pthread_mutex_lock

pthread_mutex_lock

pthread_mutex_trylock

pthread_mutex_trylock

pthread_mutex_trylock

pthread_mutex_unlock

pthread_unlock

pthread_unlock

pthread_mutexattr_create

pthread_mutexattr_init

pthread_mutexattr_init

pthread_mutexattr_

pthread_mutexattr_

pthread_mutexattr_

delete

destroy

destroy

pthread_mutexattr_

None

None

getkind_np

  

pthread_mutexattr_

None

None

setkind_np

  

pthread_once

pthread_once

pthread_once

pthread_self

pthread_self

pthread_self

pthread_setasynccancel

pthread_setcanceltype

pthread_setcanceltype

pthread_setcancel

pthread_setcancelstate

pthread_setcancelstate

pthread_setprio

pthread_setschedparam

pthread_setschedparam

pthread_setscheduler

pthread_setschedparam

pthread_setschedparam

pthread_setspecific

pthread_setspecific

pthread_setspecific

pthread_signal_to_

None

None

cancel_np

  

pthread_testcancel

pthread_testcancel

pthread_testcancel

pthread_yield

sched_yield

sched_yield

sigprocmask

pthread_sigmask

pthread_sigmask

sigwait

sigwait

sigwait


Although HP-UX 11 and Linux both implement the POSIX 1003.1, there are minor differences in platform implementations. Tables 6-16 and 6-17 show these differences.

Table 6-16. Pthread API Differences Between HP-UX and Linux

API

Difference

pthread_create

If called by more than one thread for the same tid: HP: Behavior is undefined. Linux: Returns an error (EINVAL).

pthread_default_stacksize_np

HP extension.

Linux: Use pthread_attr_setstacksize.

pthread_key_create

HP:

_SC_THREAD_KEYS_MAX = 431

_SC_THREAD_DESTRUCTOR_ITERATIONS = 430

Linux:

PTHREAD_KEYS_MAX 1024

_POSIX_THREAD_DESTRUCTOR_ITERATIONS 4

sched_get_priority_max sched_get_priority_min

Linux does not have any equivalence for HP-UX values SCHED_RR2, SCHED_RTPRIO, SCHED_NOAGE.

sched_yield

HP:

On error, returns 1 and sets errno to ENOSYS.

Linux:

On error, returns 1 and sets errno to the appropriate value.

pthread_sigmask

HP: If pending unblocked signal, at least one such signal is delivered before the function returns.

pthread_mutexattr_getspin_np pthread_mutexattr_setspin_np pthread_mutex_getyieldfreq_np pthread_mutex_setyieldfreq_np pthread_attr_getprocessor_np pthread_attr_setprocessor_np

HP extensions.


Table 6-17. Default Pthreads Attribute Values for HP-UX and Linux

Attribute

HP-UX 11i v2

Linux 2.6

contention scope

PTHREAD_SCOPE_PROCESS

PTHREAD_SCOPE_SYSTEM

detachstate

PTHREAD_CREATE_JOINABLE

PTHREAD_CREATE_JOINABLE

stacksize

256K

Depends on Linux installation: Fedora (2.6.91.667): 10MB SUSE 9.1: 2MB

inheritsched

PTHREAD_INHERIT_SCHED

PTHREAD_INHERIT_SCHED

schedpolicy

SCHED_TIMESHARE

SCHED_OTHER

guardsize

PAGESIZE (4K)

PAGESIZE (4K)

processor

PTHREAD_SPUINHERIT_NP

Not applicable

binding type

PTHREAD_BIND_ADVISORY_NP

Not applicable


6.10.2. Differences in HP-UX DCE and POSIX Threads

Linux and HP-UX version 11.0 provide support for POSIX threads as specified by the POSIX.1003.1 standard. Existing HP-UX applications based on DCE pthreads will need to be ported to adhere to the latest POSIX threads standards. This section shows some of the issues porting engineers face when porting from DCE-based applications to the latest POSIX threads standard.

6.10.2.1. Error Returns

All Linux NPTL pthreads functions return the error number as the return value (they also set the errno global variable). In HP-UX DCE pthreads, pthread functions that return on error return a 1 and set the errno global variable to the appropriate value. Applications written to the DCE pthreads API must be changed to handle error returns properly.

HP-UX DCE pthreads:

{int ret; int *per; ret = pthread_cond_t ( thread, &per ) ; if ( ret== -1 ) printf ( error =%d\n, errno ) ; } 


POSIX pthreads:

{int ret; int *ptr; ret = pthread_join ( thread, &ptr ) ; if ( ret ) printf ( error =%d\n, ret ) ; } 


6.10.2.2. Threading Model

DCE pthreads are implemented as user threads. This means that thread functionalities such as creating, scheduling, context switching, and terminating (and others) are mainly done in user space as opposed to kernel space. There may be some kernel interaction to attach the user threads to one or more kernel threads to run, but most of the thread activities are done within the pthreads library in user space.

Contrast this to the POSIX threads library, which may implement a 1:1 or M:N model. In 1:1 mode, major thread functionalities such as thread scheduling and dispatching are done within the kernel, whereas in the M:N mode, these functionalities are done within the context of the pthreads library in user space.

Linux only implements the 1:1 threading model as of this writing. There are no plans to support the M:N model in the future.

6.10.2.3. Sigwait() Function

There is a syntax change in sigwait() between DCE pthreads and POSIX threads. The following code snippets show how to port between sigwait versions.

HP-UX DCE pthreads:

{   ret = sigwait(set);   if (ret < 0) {   ret = errno;   } else {     *sig = ret;   ret = 0;   } } 


POSIX pthreads:

{   ret = sigwait(set, sig); } 


6.10.2.4. Mutex API Differences

DCE pthreads APIs to create and destroy mutex attributes changed in POSIX.1-1996. DCE pthreads used pthread_mutexattr_create and pthread_mutexattr_delete, whereas POSIX.1-1996 uses pthread_mutexattr_init and prhread_mutexattr_destroy, respectively.

POSIX.1003.1c introduces static initialization of mutexes. The following is an example of static initialization:

static pthread_mutex_t foo_mutex = PTHREAD_MUTEX_INITIALIZER; 


6.10.2.5. Condition Variable Differences

DCE pthreads APIs to create and destroy condition variable attributes changed in POSIX.1-1996. DCE pthreads used pthread_condattr_create and pthread_condattr_delete, whereas POSIX.1-1996 uses pthread_condattr_init and prhread_condattr_destroy, respectively.

6.10.2.6. Changes in *_r APIs

The POSIX1003.1c standard removed requirements for libc_r. Most library functions were made thread-safe without the need to modify the function interface. However, a few functions could not be made such. Read through the GNU libc documentation to make sure functions are thread-safe.

6.10.3. Debugging Linux Threads

One of the most asked questions about multithreaded applications is how to analyze and debug the application using a debugger. On HP-UX v11, the main application debugger is gdb. HP-UX developers who must port applications to Linux will find that this makes the transistion to Linux a little easier, because like HP-UX, the main debugger used on Linux is gdb. Example 6-5 shows a simple multithreaded program, how the program is compiled on a Linux system using gcc, and how the program is debugged after it produces a core file in a multithreaded application on Linux.

When using gdb to debug multithreaded applications, you need to remember only a few thread-specific commands. The other gdb commands to get stack traces and other information remain the same. The thread-specific commands for use in gdb are as follows:

  • info threads-. Inquires about existing threads.

  • thread <threadnumber>. Switches threads.

  • thread apply [threadnumber] [all] args-. Applies a command to a list of threads.

Example 6-5 is a multithreaded program that creates four threads. Each thread has its own function to performthat is, to raise an exception and cause the program to terminate and create a core file. Note that on some Linux systems, a core file is not automatically generated unless the command ulimit -c <filesize> is executed from the shell. Only then will it produce a core file in the directory where the executable resides that will be named core.<pid>, where the pid is the process ID of the process that produced the core file.

Example 6-5. Listing of thread_1.c

 #include <pthread.h> #include <signal.h> #include <errno.h> #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define THREAD_NUM 4 pthread_mutex_t mutex; pthread_cond_t cond; static int thread_in_wait = 0; void * abort_func(void *arg) {   printf("in abort_func\n");   pthread_mutex_lock(&mutex);   thread_in_wait++;   pthread_cond_wait(&cond, &mutex);   thread_in_wait--;   while (thread_in_wait != 0)   {     pthread_mutex_unlock(&mutex);     sched_yield();     pthread_mutex_lock(&mutex);   }   pthread_mutex_unlock(&mutex);   raise(SIGABRT); } void * segv_func(void *arg) {   printf("in segv_func\n");   pthread_mutex_lock(&mutex);   thread_in_wait++;   pthread_cond_wait(&cond, &mutex);   thread_in_wait--;   while (thread_in_wait != 0)   {     pthread_mutex_unlock(&mutex);     sched_yield();     pthread_mutex_lock(&mutex);   }   pthread_mutex_unlock(&mutex);   raise(SIGSEGV); } void * bus_func(void *arg) {   printf("in bus_func\n");   pthread_mutex_lock(&mutex);   thread_in_wait++;   pthread_cond_wait(&cond, &mutex);   thread_in_wait--;   while (thread_in_wait != 0)   {     pthread_mutex_unlock(&mutex);     sched_yield();     pthread_mutex_lock(&mutex);   }   pthread_mutex_unlock(&mutex);   raise(SIGBUS); } void * fpe_func(void *arg) {   printf("in fpe_func\n");   pthread_mutex_lock(&mutex);   thread_in_wait++;   pthread_cond_wait(&cond, &mutex);   thread_in_wait--;   while (thread_in_wait != 0)   {     pthread_mutex_unlock(&mutex);     sched_yield();     pthread_mutex_lock(&mutex);   }   pthread_mutex_unlock(&mutex);   raise(SIGFPE); } void *(*func_array[THREAD_NUM])(void *) = {             bus_func,             segv_func,             abort_func,             fpe_func             }; int main(void) {   pthread_t tid[THREAD_NUM];   int i = 0;   int rc = 0;   void *(*fp[THREAD_NUM])(void *);   pthread_cond_init(&cond, NULL);   pthread_mutex_init(&mutex, NULL);   for (i=0; i<THREAD_NUM; i++)   {     fp[i] = func_array[i];   }   for (i=0; i<THREAD_NUM; i++)   {     if( (rc = pthread_create(&tid[i], NULL, fp[i], NULL )) != 0)     {       printf("pthread_create failed: %d\n", errno);       exit (1);     }   }   pthread_mutex_lock(&mutex);   while (thread_in_wait != THREAD_NUM)   {     pthread_mutex_unlock(&mutex);     sched_yield();     pthread_mutex_lock(&mutex);   }   pthread_mutex_unlock(&mutex);   pthread_cond_broadcast(&cond);   for (i=0; i<THREAD_NUM; i++)   {     pthread_join(tid[i], NULL);   }   return (0); } 

Compile Linux using gcc:

$ gcc thread_1.c -o thread_1 -lpthread -D_REENTRANT -g 


Run the program:

$ ./thread_1 in bus_func in segv_func in abort_func in fpe_func Floating point exception (core dumped) 


Analyze the program using gdb:

$ gdb thread_1 core.6797        Note that on Linux a core                                 file is produced with the                                 process id as the extension. GNU gdb 6.1 Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-suse-linux"...Using host libthread_db library "/lib/tls/libthread_db.so.1". Core was generated by './thread_1'. Program terminated with signal 8, Arithmetic exception. warning: current_sos: Can't read pathname for load map: Input/output error Reading symbols from /lib/tls/libpthread.so.0...done. Loaded symbols for /lib/tls/libpthread.so.0 Reading symbols from /lib/tls/libc.so.6...done. Loaded symbols for /lib/tls/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0xffffe410 in ?? () (gdb) info thread             Command to display all                               threads in the process  5 process 6797 0xffffe410 in ?? ()  4 process 6798 0xffffe410 in ?? ()  3 process 6799 0xffffe410 in ?? ()  2 process 6800 0xffffe410 in ?? () * 1 process 6801 0xffffe410 in ?? ()   Notice the asterisk in                                        front of the thread. This                                        denotes that the current                                        thread context is thread                                        number 1. 


The command info threads displays a summary of all threads currently in the program. In the example output, from left to right, they are as follows:

  1. The thread number assigned by gdb

  2. The target systems thread identifier (systag, second and third column)

  3. The current stack frame summary for the thread

The asterisk (*) to the left of the gdb tHRead number indicates the current thread.

On an HP-UX machine, the following appears for the same program.

#0 0xc020c4b0 in kill+0x10 () from /usr/lib/libc.2 (gdb) info thread *  5 system thread 1440101 0xc020c4b0 in kill+0x10 () from /usr/lib/libc.2   4 system thread 1440100 0xc020ed60 in sched_yield+0x10 ()   from /usr/lib/libc.2   3 system thread 1440099 0xc020ed60 in sched_yield+0x10 ()   from /usr/lib/libc.2   2 system thread 1440098 0xc020ed60 in sched_yield+0x10 ()   from /usr/lib/libc.2   1 system thread 1440097 0xc020b318 in __kwakeup+0x10 ()   from /usr/lib/libc.2 


Notice that on HP-UX, systag denotes a system thread rather than a process on the Linux system from the output when reading a core file.

Continuing with our example:

(gdb) bt                Command to display the                         stack traceback of the                         current thread. #0 0xffffe410 in ?? () #1 0x40952a78 in ?? () #2 0x00000008 in ?? () #3 0x00001a91 in ?? () #4 0x400331a1 in raise () from /lib/tls/libpthread.so.0 #5 0x08048997 in fpe_func (arg=0x0) at thread_1.c:98 #6 0x4002d9dd in start_thread () from /lib/tls/libpthread.so.0 #7 0x400efffa in clone () from /lib/tls/libc.so.6 


At the time of the core dump, the current thread is always the last running thread and would likely have caused the exception. Examining the stack through the bt command leads us to the calling function, which was raise. raise was called from our application in the thread_1.c file and shows the corresponding line number.

We continue with the example by switching to other threads and examining their stack traces:

(gdb) thread 2          Command to switch to                         thread #2. [Switching to thread 2 (process 6800)]#0 0xffffe410 in ?? () (gdb) bt                 Issue command to display                          the stack of thread #2. #0 0xffffe410 in ?? () #1 0x40751a78 in ?? () #2 0x00000006 in ?? () #3 0x00001a90 in ?? () #4 0x400331a1 in raise () from /lib/tls/libpthread.so.0 #5 0x080487cf in abort_func (arg=0x0) at thread_1.c:35 #6 0x4002d9dd in start_thread () from /lib/tls/libpthread.so.0 #7 0x400efffa in clone () from /lib/tls/libc.so.6 (gdb) thread 5 [Switching to thread 5 (process 6797)]#0 0xffffe410 in ?? () (gdb) bt #0 0xffffe410 in ?? () #1 0xbffff528 in ?? () #2 0x00001a8e in ?? () #3 0x00000000 in ?? () #4 0x4002df2f in pthread_join () from /lib/tls/libpthread.so.0 #5 0x08048ad9 in main () at thread_1.c:145 (gdb) quit 


In most cases when a multithreaded program dumps a core, the most likely thread to have caused the dump is the current running thread. In some cases, however, when the application hangs, you may need to attach gdb to the running program. See Chapter 7 or refer to the GNU gdb manual[32] online for information about how to attach to a currently running program.

[32] www.gnu.org/software/gdb/documentation/

6.10.4. C++ Exceptions and Linux Threads

Searching the Internet for related topics on C++ exceptions and POSIX threads will yield a variety of opinions regarding how C++ should handle POSIX threads 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 that follow involve simple C++ routines mixed with POSIX threads. Compiling and running these programs in both HP-UX using aCC (B3910B A.03.50) and SUSE 9.1 Linux with g++ version 3.3.3 yields different results (see Example 6-6).

Example 6-6. Listing of exception_1.cc

 #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.cc 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.cc on HP-UX yields the following:

$ aCC exception_3.cc  -o exception_3 -lpthread -D_REENTRANT -g $ ./exception_3 No core dumped 


In this case, the pthread_cancel within the destructor of FooClass is considered an exception. 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 to catch the exception in the following code snippet. Unfortunately, this solution did not work in our case:[33]

[33] Not because the solution is wrong for C++ applications, but because the C++ standard does not take into consideration POSIX threads semantics

~FooClass() {              try {            pthread_cancel(pthread_self());            pthread_testcancel();               }         catch (...)               {           // do whatever     throw;     } } 


One solution to try to 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);     } 


Next, Example 6-7 shows a program that involves canceling a thread from another thread.

Example 6-7. Listing of exception_2.cc

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <pthread.h> pthread_mutex_t mutex; static int instance_count; class  FooBar {   public:     FooBar();     ~FooBar(); }; FooBar::FooBar() {   printf("thr#%u: creating FooBar.\n", pthread_self());   {     pthread_mutex_lock(&mutex);     ++instance_count;     pthread_mutex_unlock(&mutex);   } } FooBar::~FooBar() {   printf("thr#%u: destroying FooBar.\n", pthread_self());   {     pthread_mutex_lock(&mutex);     --instance_count;     pthread_mutex_unlock(&mutex);   } } static void* thread_func(void*) {   FooBar   f;   pthread_t this_thread = pthread_self();   printf("thread#%u: about to sleep...\n", pthread_self());   sleep(10);   return 0; } int main() {   pthread_t tid;   int rc = 0;   pthread_mutex_init(&mutex, NULL);   if ((rc = pthread_create(&tid, NULL, thread_func, NULL)) != 0)   {     printf("pthread_create() returned %d. %s\n", errno);     return 1;   }   printf("Main thread cancelling thr#%u\n", tid);   if ((rc = pthread_cancel(tid)) != 0)   {     printf("pthread_cancel() : %s\n", errno);   }   pthread_join(tid, NULL);   printf("Main thread join'ed with thr#%u\n", tid);   pthread_mutex_lock(&mutex);   if (instance_count == 0)   {     printf("OK. instance_count = 0\n");   }   else   {     printf("ERROR! instance_count=%d (expected 0)\n",                        instance_count);   }   pthread_mutex_unlock(&mutex);   return (0); } 

The program in Example 6-7 shows how thread cancellation, even when used outside of destructors, may show different behaviors on different platforms.

Compiling and running exception_2.cc on Linux yields the following:

$ g++ exception_2.cc -o exception_2 -lpthread -D_REENTRANT $ ./exception_2 thr#1078156208: creating FooBar. thread#1078156208: about to sleep... Main thread cancelling thr#1078156208 thr#1078156208: destroying FooBar. Main thread join'ed with thr#1078156208 OK. instance_count = 0 


Compiling and running exception_2.cc on HP-UX yields the following:

$ aCC exception_2.cc  -o exception_2 -lpthread -D_REENTRANT -g $ ./exception_2 thr#2: creating FooBar. thread#2: about to sleep... Main thread cancelling thr#2 Main thread join'ed with thr#2 ERROR! foobar_instcount=1 (expected 0) 


The program for listing exception_2.cc seems to have been handled well on a Linux system but not on HP-UX. So, it seems Linux handles thread cancellation better in some way.

Example 6-8 involves the simple throwing of exceptions within threads. It also shows how mixing POSIX threads and C++ can yield different results on different platforms.

Example 6-8. Listing of exception_3.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_3.cc on Linux yields the following:

$ g++ exception_3.cc -o exception_3 -lpthread -D_REENTRANT $ ./exception_3 throwing check Aborted (core dumped) 


Compiling and running exception_3.cc on HP-UX yields the following:

$ aCC exception_3.cc -o exception_3 -lpthread -D_REENTRANT -g $ ./exception_3 throwing check Aborted (core dumped) 


Changing thread_func to throw an integer rather than a char * changes the behavior of the program to complete its run without aborting:[34]

[34] The sample exception_3.cc compiled and ran fine on AIX 5.3 using an 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 was 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 a program listing exception_3.cc, the problem seems to be that of uncaught exceptions. Again referring to the book The C++ Programming Language 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 preceeding program examples clearly show that C++ exceptions and POSIX threads do not mix well. The consensus from porting experience[35] indicates that there is no well-defined standard for how C++ needs to handle pthread_cancellations and exceptions, especially within destructors.[36] The one 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.

[35] As well as comments from comp.programming.threads newsgroup

[36] ANSI C++ does not recommend throwing exceptions in the constructor and destructor of a class whose instances may be defined globally (static-globally).

6.10.5. Linking with Thread-Safe Libraries

Sometimes nonthreaded software applications may be linked to thread-safe libraries that call routines that start with pthread_*. In HP-UX, nonthreaded libraries that fail because of unresolved symbols such as pthread_* can be linked to the threads library libpthreads or libcma to resolve these symbols. However, linking such libraries to an application may result in overhead because of threading logic that accompanies linking of these threaded libraries. To resolve the performance degradation, HP implemented pthread_* calls as stubs in the C language library. Therefore, in HP-UX, nonthreaded applications that reference calls with pthread_* call the stubs in libc, thus avoiding performance overhead associated when linking with threads libraries.

On Linux, nonthreaded applications that link with thread-safe libraries must be linked with the LinuxThreads or Linux NPTL threads library to resolve symbols referenced within the linked libraries.




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