6.10. ThreadsThis 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:
$ 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 LinuxHP-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.
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.
6.10.2. Differences in HP-UX DCE and POSIX ThreadsLinux 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 ReturnsAll 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 ModelDCE 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() FunctionThere 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 DifferencesDCE 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 DifferencesDCE 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 APIsThe 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 ThreadsOne 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:
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
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:
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.
6.10.4. C++ Exceptions and Linux ThreadsSearching 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
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:
Dr. Stroustrup offers a solution to catch the exception in the following code snippet. Unfortunately, this solution did not work in our case:[33]
~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
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
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]
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.
6.10.5. Linking with Thread-Safe LibrariesSometimes 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. |