Debugging Multithreaded Programs

Table of contents:

Writing multithreaded programs that execute correctly can be quite a challenge. Fortunately, there are some tools available to help with the task. Many current C, C++ compilers are bundled with thread-aware debuggers . For example, newer versions of the GNU C, C++ compiler gcc , g++ , come with gdb , and Solaris' C, C++ compiler comes with dbx . Thread-aware debuggers automatically recognize multithreaded code. Such debuggers can be used to step through multithreaded programs and examine the contents of mutexes and TSD.

We will use Program 11.11 as source for our thread-debugging example. The debugger presented will be GNU's gdb (version 5.1.90CVS-5.) [24] As presented, this program is syntactically correct but contains logic errors pertaining to the access and manipulation of common data by the multiple detached threads.

[24] Only the command-line version of the debugger will be addressed. GNU also provides a graphical interface for its debugger called xxgdb for those who are working in a windowing environment.

Program 11.11 Debugging multithreaded programs.

File : p11.11.cxx
 /*
 Debugging multithreaded prgrms - WITH LOCKING ERRORS
 Compile: g++ p11.11.cxx -lpthread -o p11.11
 */
 + #define _REENTRANT
 #define _GNU_SOURCE
 #include 
 #include 
 #include 
 10 #include 
 #include 
 #include 
 using namespace std;
 const int MAX=5,
 + HOME=25;
 int
 my_rand(int start, int range){
 struct timeval t;
 gettimeofday(&t, (struct timezone *)NULL);
 20 return (int)(start+((float)range * rand_r((unsigned *)&t.tv_usec))
 / (RAND_MAX+1.0));
 }
 typedef struct {
 int increment;
 + char *phrase;
 } argument;
 void step( void * );
 // common to all threads
 pthread_t thread_id[MAX];
 30 bool alive = true, home = false;
 int position,total=0;
 char walk[] = " ";
 int
 main(int argc, char *argv[]) {
 + argument right={ +1, "ZOINK! Stepped off the RIGHT side.
"},
 left ={ -1, "SPLAT! Stepped off the LEFT side.
"};
 pthread_attr_t attr_obj;
 if (argc < 2) { /* check arg list */
 cerr << *argv << " start_position" << endl;
 40 return 1;
 }
 position = atoi(argv[1]);
 if ( position < 1 )
 position = 1;
 + else if ( position > MAX )
 position = MAX;
 walk[position+5] = '*';
 setvbuf(stdout, (char *) NULL, _IONBF, 0);
 cout << "The drunken sailor walk" << endl << endl;
 50 cout << " +12345+" << endl;
 cout << walk << endl;
 pthread_attr_init( &attr_obj );
 pthread_attr_setdetachstate( &attr_obj, PTHREAD_CREATE_DETACHED );
 pthread_create(&thread_id[0], &attr_obj,
 + (void *(*) (void *)) step, (void *) &right);
 pthread_create(&thread_id[1], &attr_obj,
 (void *(*) (void *)) step, (void *) &left );
 pthread_exit(NULL);
 return 0;
 60 }
 void
 step( void *a ) {
 argument *my_arg=(argument *)a;
 do {
 + sleep( my_rand(1,3) ); // pause a bit
 walk[position+MAX] = ' '; // clear old position
 position += my_arg->increment; // calculate new position
 alive = bool(position > 0 && position <= MAX);
 walk[position+MAX] = alive ? '*' : '$';
 70 cout << walk << endl;
 home = bool(++total >= HOME);
 if ( !alive home ) {
 if ( !alive )
 cout << my_arg->phrase;
 + else
 cout << "The sailor made it home safely this time!
";
 pthread_kill(thread_id[ (position < 1 ? 1 : 0)], 9);
 }
 sched_yield( );
 80 } while ( alive && !home );
 }

Program 11.11 contains an assortment of POSIX thread calls. The program, which is purely pedagogical in nature, implements a version of the "drunken sailor" problem. In this version, a drunken sailor is given a starting position on a boardwalk that is five steps wide. The program traces the path of the sailor as he or she progresses down the boardwalk toward home (located an arbitrary number of steps from the start). If the sailor steps off either side of the boardwalk, he or she perishes. If the sailor is still on the boardwalk after a set number of steps he or she is considered to have made it home. The sailor's position on the boardwalk is stored in a variable called position . Two threads manipulate this data. One thread executes a user -defined function, step , moving the sailor to the right, while a second thread executes the same function, moving the sailor to the left (the movement is based on the argument passed to the step function). Both threads are detached from the initiating thread. When the sailor perishes or reaches the end of the walk, the detached threads are terminated . Typical output from Program 11.11 is shown in Figure 11.19.

Figure 11.19. Several runs of Program 11.11.

graphics/11fig19.gif

In the first run it appears that the program is working pretty much as would be expected. However, the second and third run produces somewhat unexpected results. In the second run it looks as if there might be two sailors on the boardwalk (I suppose one could be seeing doublebut this is not the case). In the third run the right side of the boardwalk seems to have disappeared. Clearly, something funny is going on! The problem is tied to the unrestricted access of common data by competing threads. One way to check on what is happening is to run the program in the debugger.

To prepare the program for the debugger, pass the -g argument at compilation time to prevent the automatic removal of additional symbol table information from the executable. For example, the command sequence

linux$ g++ -g p11.11.cxx -lpthread -o p11.11

produces an executable, p11.11 , that can be loaded and run in the debugger. When the debugger is invoked, it is passed the name of the executable. For our example this would be

linux$ gdb p11.11
GNU gdb Red Hat Linux (5.1.90CVS-5)
Copyright 2002 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 "i386-redhat-linux"...
(gdb)

Suppose we want the debugger to stop in the user-defined function step . We can use the list command in gdb to show us a given sequence of lines (with their line numbers ). For example,

(gdb) list 61,81
61 void
62 step( void *a ) {
63 argument *my_arg=(argument *)a;
64 do {
65 sleep( my_rand(1,3) ); // pause a bit
66 walk[position+MAX] = ' '; // clear old position
67 position += my_arg->increment; // calculate new position
68 alive = bool(position > 0 && position <= MAX);
69 walk[position+MAX] = alive ? '*' : '$';
70 cout << walk << endl;
71 home = bool(++total >= HOME);
72 if ( !alive home ) {
73 if ( !alive )
74 cout << my_arg->phrase;
75 else
76 cout << "The sailor made it home safely this time!
";
77 pthread_kill(thread_id[ (position < 1 ? 1 : 0)], 9);
78 }
79 sched_yield( );
80 } while ( alive && !home );
81 }

Or, we can also use the list command and pass the name of the user-defined function we would like to see listed (such as step ). If we do this, the debugger will show the first N (usually 10) lines of the referenced function. The listing usually begins a line or two prior to the actual function.

(gdb) list step
57 (void *(*) (void *)) step, (void *) &left );
58 pthread_exit(NULL);
59 return 0;
60 }
61 void
62 step( void *a ) {
63 argument *my_arg=(argument *)a;
64 do {
65 sleep( my_rand(1,3) ); // pause a bit
66 walk[position+MAX] = ' '; // clear old position

To stop at line 66, we establish a breakpoint.

(gdb) break 66
Breakpoint 1 at 0x8048b61: file p11.11.cxx, line 66.

To execute (run) the program, the run command is used. Any values that would normally be passed on the command line are placed after the run command.

(gdb) run 5
Starting program: /home/faculty/gray/revision/11/sailor/p11.11 5
[New Thread 1024 (LWP 3176)]
The drunken sailor walk
 +12345+

<-- 1

*
[New Thread 2049 (LWP 3193)]
[New Thread 1026 (LWP 3194)]
[New Thread 2051 (LWP 3195)]
[Switching to Thread 1026 (LWP 3194)]
Breakpoint 1, step (a=0xbffffb48) at p11.11.cxx:66
66 walk[position+MAX] = ' '; // clear old position

(1) General program output.

When the debugger stops at the indicated line, the command info thread can be issued to obtain a wealth of thread information.

(gdb) info thread
 4 Thread 2051 (LWP 3195) 0x420b4b31 in nanosleep () from /lib/i686/libc.so.6
* 3 Thread 1026 (LWP 3194) step (a=0xbffffb48) at p11.11.cxx:66
 2 Thread 2049 (LWP 3193) 0x420e0037 in poll () from /lib/i686/libc.so.6
 1 Thread 1024 (LWP 3176) 0x420292e5 in sigsuspend () from /lib/i686/libc.so.6

The astute reader will notice a number of things. Thread 1 (the initiating thread) was directed to exit (line 58, pthread_exit(NULL); ) but at this juncture still appears to be active. At present, there are four threads associated with the program. The current active thread, identified with an asterisk, is thread ID 3, which is associated with LWP 3194.

The command display variable_name , where variable_name is the name of the variable of interest, directs the debugger to display the current contents of the variable each time a breakpoint is encountered . In the sequence below we have directed the debugger to display the contents of the global variables alive , position , and home before we issue run .

.
.
.
Starting program: /home/faculty/gray/revision/11/sailor/p11.11 5
[New Thread 1024 (LWP 3274)]
The drunken sailor walk

 +12345+
 *
[New Thread 2049 (LWP 3291)]
[New Thread 1026 (LWP 3292)]
[New Thread 2051 (LWP 3293)]
[Switching to Thread 1026 (LWP 3292)]

Breakpoint 1, step (a=0xbffffb48) at p11.11.cxx:66
66 walk[position+MAX] = ' '; // clear old position
3: home = false
2: position = 5

<-- 1

1: alive = true
(gdb) cont
Continuing.
 $
ZOINK! Stepped off the RIGHT side.
[Switching to Thread 2051 (LWP 3293)]

Breakpoint 1, step (a=0xbffffb40) at p11.11.cxx:66
66 walk[position+MAX] = ' '; // clear old position
3: home = false
2: position = 6

<-- 2

1: alive = false
(gdb) cont
Continuing.
 *

Breakpoint 1, step (a=0xbffffb40) at p11.11.cxx:66
66 walk[position+MAX] = ' '; // clear old position
3: home = false
2: position = 5
1: alive = true
.

<-- 3

.
.

(1) At this point the sailor, at position 5, has not reached home and is still alive.

(2) Now the sailor is at position 6. He or she has not reached home and is no longer alive. The program should stop here, but it does not.

(3) Suddenly, the sailor is at position 5. While still having reached home, the sailor is now alive! Clearly, the thread doing the decrement to the position has performed its activity before the test for being alive was done.

A specific thread can be referenced with the command thread N, where N is the number of the appropriate thread. As shown below, information specific to the thread can be referenced once the thread is loaded.

(gdb) thread 4
[Switching to thread 4 (Thread 2051 (LWP 3342))]#0 step (a=0xbffffb40) at
p11.11.cxx:66
66 walk[position+MAX] = ' '; // clear old
 position
(gdb) print *my_arg
 = {increment = -1, phrase = 0x8048da0 "SPLAT! Stepped off the LEFT side.
"}

<-- 1

(gdb) thread 3
[Switching to thread 3 (Thread 1026 (LWP 3341))]#0 step (a=0xbffffb48) at p11.11.cxx:66
66 walk[position+MAX] = ' '; // clear old
 position
(gdb) print *my_arg
 = {increment = 1, phrase = 0x8048d60 "ZOINK! Stepped off the RIGHT
 side.
"}

(1) This is the thread that does the decrement.

Anytime the debugger is stopped , the contents of a mutex can be displayed ( assuming it is within the current scope). For example, if we had a mutex called my_lock , its contents before it is acquired would be

(gdb) print my_lock
 = {__m_reserved = 0, __m_count = 0, __m_owner = 0x0, __m_kind = 0,
 __m_lock = {

__status = 0,

__spinlock = 0}}

<-- 1

(gdb) print my_lock

<-- 1

= {__m_reserved = 0, __m_count = 0, __m_owner = 0x0, __m_kind = 0,
 __m_lock = {

__status = 1,

__spinlock = 0}}

(1) This member is set to 1 when the mutex is locked.

and after it is acquired

(1) This member is set to 1 when the mutex is locked.

The quit command is used to leave the debugger. An abbreviated listing of gdb commands can be displayed in gdb using the command help . The manual pages on gdb contain a more detailed explanation of how to invoke gdb . On the command line, info gdb provides a wealth of information on how to use gdb (including a fairly detailed sample session).

EXERCISE

Modify Program 11.11 to support two (or more) sailors walking down the boardwalk at the same time (you may need to increase the width of the boardwalk). Use a different symbol for each sailor. No two sailors should occupy the same location at the same time (it's the law!). The program should end when either, all sailors have expired , or one or more sailors have reached their goal, say a set number of steps.

Programs and Processes

Processing Environment

Using Processes

Primitive Communications

Pipes

Message Queues

Semaphores

Shared Memory

Remote Procedure Calls

Sockets

Threads

Appendix A. Using Linux Manual Pages

Appendix B. UNIX Error Messages

Appendix C. RPC Syntax Diagrams

Appendix D. Profiling Programs



Interprocess Communication in Linux
Interprocess Communications in Linux: The Nooks and Crannies
ISBN: 0130460427
EAN: 2147483647
Year: 2001
Pages: 136

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