Thread-Specific Data

Sometimes we need to be able to maintain data that is specific to a given thread but is referenced in a section of code that will be executed by multiple threads. Data of this nature, stored in a memory block private to the thread, is usually termed thread-specific data, or TSD. This data is referenced using a thread-specific pointer and the associated key. The keys for TSD are global to all the threads within the process. To make use of TSD, a thread must allocate and bind (associate) the key with the data. The library call pthread_key_create (Table 11.29) is used to allocate a new key. Only one thread should issue the create call for a TSD item.

Table 11.29. The pthread_key_create Library Function.

Include File(s)

Manual Section

3

Summary

int pthread_key_create( pthread_key_t *key, void(*destr_function) (void *) );

Return

Success

Failure

Sets errno

-1

 

As its first argument, the pthread_key_create library call is passed a reference, *key , to a pthread_key_t data type. The pthread_key_t data type is typedef ed as an unsigned int . If the call to pthread_key_create is successful, the *key argument references the newly allocated key. There is a system limit on the number of keys per thread. The limit is designated by the defined constant PTHREAD_KEYS_MAX.

The second argument for pthread_key_create is a reference to a destructor function. If this argument is non-NULL, the referenced function is called and passed the associated TSD when the thread exits (i.e., calls pthread_exit ) or is cancelled. If TSD is allocated within the destructor function, the system attempts to repeat destructor calls until all keys are NULL. As might be expected, this behavior could lead to some interesting recursive situations. Some thread implementations , such as LinuxThreads, limit the number of calls to resolve the removal of TSD; some do not. In LinuxThreads these calls are limited by the defined constant PTHREAD_DESTRUCTOR_ITERATIONS. If the call to pthread_key_create is successful, it returns a value of 0; otherwise , it returns the value EAGAIN (11) if the limit for the number of keys has been exceeded or ENOMEM (12) if insufficient memory is available to allocate the key.

The pthread_key_delete library function (Table 11.30) is used to remove the storage associated with a specific key (versus the data associated with the key).

Table 11.30. The pthread_key_delete Library Function.

Include File(s)

Manual Section

3

Summary

int pthread_key_delete(pthread_key_t key);

Return

Success

Failure

Sets errno

-1

 

A valid TSD key is pthread_key_delete 's only argument. If the key value is non-NULL, it is removed. While it would seem reasonable, the pthread_key_delete function does not automatically call the key 's associated destructor function. If the function is passed an invalid key , it returns the value EINVAL (22); otherwise, it returns a 0, indicating successful removal of the key .

TSD is manipulated with the pthread_setspecific and pthread_getspecific library calls (Table 11.31).

Both functions accept a globally allocated key argument. The key , created by a call to pthread_key_create , is used by the pthread_setspecific library function to store the data referenced by the pointer argument. By definition, each calling thread can bind a different data value with the key . Most often, pointer references memory that has been dynamically allocated by the calling thread. Once bound, the associated values are individually maintained on a per-thread basis. The pthead_setspecific function will fail and return the value ENOMEM (12) if there is insufficient memory to associate a value with a key. If successful, pthread_setspecific returns a 0. The pthread_getspecific library function uses the key argument to retrieve (return) a reference to the TSD. If the key is not bound, a NULL (0) is returned.

Table 11.31. The TSD manipulation Library Functions.

Include File(s)

Manual Section

3

Summary

int pthread_setspecific(pthread_key_t key, const void *pointer); void *pthread_getspecific(pthread_key_t key);

Return

Success

Failure

Sets errno

-1

 

Program 11.9 demonstrates one approach for using TSD.

Program 11.9 Using TSD.

File : p11.9.cxx /* Using thread specific data */ #define _GNU_SOURCE + #define _REENTRANT #include #include #include #include 10 using namespace std; const int MAX=20; void *TSD( int ), // manipulates TSD free_me( void * ); // destructor static pthread_key_t key; // global TSD key + int main( int argc, char *argv[] ) { pthread_t thr_id[MAX]; int inc; if ( argc < 2 atoi(argv[1]) > MAX){ 20 cerr << *argv << " num_threads" << endl; return 1; } // generate key (once) pthread_key_create(&key, (void(*)(void*))free_me); + for(int i=0; i < atoi(argv[1]); ++i){ inc = i+1; // can't cast an expr if (pthread_create(&thr_id[i],NULL,(void *(*)(void *))TSD, (void *)inc) > 0){ cerr << "pthread_create failure" << endl; return 2; 30 } } // wait for all threads for(int i=0; i < argc-1; ++i) pthread_join(thr_id[i], NULL); + sleep( 1 ); return 0; } /* TSD routine - passed a value that it will keep private 40 */ void * TSD( int private_stuff ){ static pthread_mutex_t the_lock; void *tsd = NULL; + tsd = pthread_getspecific(key); // initially NULL if (tsd == NULL) { tsd = new pthread_key_t; // create storage tsd = &private_stuff; // make the association pthread_setspecific(key, tsd); 50 cout << pthread_self( ) << " TSD starts at " << *(int *)pthread_getspecific(key) << endl; } for( int i=0; i < 3; ++i ){ sleep(1); + pthread_mutex_lock(&the_lock); // enter critical region cout << pthread_self( ) << " incrementing" << endl; *(int *)pthread_getspecific(key) *= 2; // double private value cout << pthread_self( ) << " yielding" << endl; pthread_mutex_unlock(&the_lock); // exit critical region 60 sched_yield(); // notify scheduler } cout << pthread_self( ) << " TSD finishes at " << *(int *)pthread_getspecific(key) << endl; cout.flush( ); + pthread_exit(NULL); return NULL; } /* Dummy destructor routine 70 */ void free_me( void *value ){ cout << pthread_self( ) << " free reference to " << *(int *) value << endl; + }

The prototype for the user -defined function TSD that will manipulate the TSD and the user-defined function free_me that will act as a destructor are placed prior to main . In addition, the key that will be used to access TSD is allocated prior to main . Its placement assures it will be global in scope. In main , a call is made to pthread_key_create (line 24), and the addresses of the key and the destructor function are passed. A cast is used to keep the compiler from complaining about argument type mismatch. In the first for loop in main , the value of the loop counter plus one is assigned to the variable inc . [20] As each thread is created, it is passed a reference to the user-defined TSD function and the value stored in inc . Again, the cast operator is used to keep the compiler from flagging what it would consider to be mismatched arguments. Once the threads have been generated, main uses a second for loop with a call to pthread_join to wait for the threads to finish their activities. A call to sleep follows the pthread_join loop to allow the final terminating thread sufficient time to flush its output buffer.

[20] Note that the inc variable is used as a temporay storage location, as in this setting it is not legal to pass a reference to an expression.

In the user-defined function TSD a mutex, the_lock , and a void pointer, tsd , are allocated. The storage class for the_lock is static , while the storage class for tsd is auto (the default). A call is made to pthread_getspecific , and the value returned is assigned to tsd . If the key passed to pthread_getspecific has not been associated with a TSD value, the pthread_getspecific call will return a NULL value. If the returned value is NULL (which it should be upon initial entry), a storage location is allocated using the new operator (line 47). The TSD is associated with the key using the pthread_setspecific library function. Next , a for loop is used to simulate activity. Within the loop, a mutex called the_lock is used to bracket a section of code where we would like to keep our display messages from being interleaved with those of other threads that will be executing the same code. In this section of code the TSD is multiplied by 2. A cast is used to coerce the void * to an int * . A call to library function sched_yield causes the current thread to yield its execution to another thread with the same or greater priority. At different points within the program, informational messages tagged with the thread ID are displayed.

A run of Program 11.9 is shown in Figure 11.16.

Figure 11.16 A run of Program 11.9 with four competing threads.

1026 TSD starts at 1 <-- 1 2051 TSD starts at 2 3076 TSD starts at 3 4101 TSD starts at 4 1026 incrementing 1026 yielding 4101 incrementing 4101 yielding 3076 incrementing 3076 yielding 2051 incrementing <-- 2 2051 yielding 1026 incrementing 1026 yielding 4101 incrementing 4101 yielding 3076 incrementing 3076 yielding 2051 incrementing 2051 yielding 1026 incrementing 1026 yielding 1026 TSD finishes at 8 <-- 3 1026 free reference to 8 4101 incrementing 4101 yielding 3076 incrementing 3076 yielding 2051 incrementing 2051 yielding 4101 TSD finishes at 32 4101 free reference to 32 3076 TSD finishes at 24 3076 free reference to 24 2051 TSD finishes at 16 2051 free reference to 16

(1) Each thread has a different starting value for its data.

(2) Each thread accesses its private data, increments it and then yields three times for each tread.

(3) As each thread finishes, it releases its reference to the TSD. The order of processing is determined by the scheduler and may vary across multiple invocations.

As can be seen in Figure 11.16, each thread maintains its own TSD value. The call to the destructor function free_me is made as each thread finishes its execution.

In Program 11.9 we allocated the key for the TSD prior to main and created the key in main . We can, if we are careful, allocate and create the key within the code segment shared by multiple threads. For example, in Program 11.9 we can remove (comment out) the statement prior to main that allocates the key (line 14)

// static pthread_key_t key;

and the pthread_key_create statement (line 24) within main

// pthread_key_create(&key, (void(*)(void*))free_me);

and add the following statements (lines 45 to 53) to the user-defined TSD function.

/* TSD routine - passed a value that it will keep private 40 */ void * TSD( int private_stuff ){ static pthread_mutex_t the_lock; void *tsd = NULL; + // do once static pthread_key_t key; static int done_once = 0; if ( !done_once ) { pthread_mutex_lock(&the_lock); // bracket code 50 if ( !done_once++ ) // re-check & inc pthread_key_create(&key, (void(*)(void*))free_me); pthread_mutex_unlock(&the_lock); // end bracket } tsd = pthread_getspecific(key); // initially NULL

In this second approach the storage class qualifier static is used in the user-defined TSD function when allocating the key . An integer flag variable called done_once , specified as static , is also allocated and initially set to 0 (which with the static qualifier should be its default value anyway). The mutex is used to bracket the inner check of the content of the done_once variable. Only one thread will find the done_once variable at 0, increment the variable, and create the key . After the done_once variable is incremented, the outer if will prevent further access to this section of code. This is by no means the only way in which this can be done. Another approach is to use the pthread_once library function. [21]

[21] If this were a text on just threads, an examination of pthread_once would be in order. However, as this is only an introduction, we will skip the details concerning pthread_once at this time and direct the interested reader to the associated manual page on pthread_once .

This section ends with a final program example, Program 11.10, that uses TSD and incorporates a number of the previously discussed thread- related functions. In this example the program is passed the name of a file to store its output and the number of threads (110) to generate. Each thread, represented by a different uppercase letter of the alphabet, is responsible for moving about within a common two-dimensional grid. As the thread moves, its path is displayed on the screen. The starting point for a thread is marked by an @ symbol. When the thread moves to a new location, its previous location is changed to lowercase. As written, a thread can move in one of four directions: left, right, up, or down. Moves that run off the grid are wrapped, in a modular fashion, to the next valid rowcolumn location. If a thread moves to a location that is boxed in (i.e., a location where its neighbors on the left, right, top, and bottom are occupied), the thread expires . The program terminates when all threads have been boxed in.

To make the output a bit more interesting, the current state of the grid is displayed using basic vt100 escape codes. [22] The vt100 escape codes are incorporated in the user-defined functions LOCATE (used to place the cursor at a specific screen coordinate) and CLS (used to clear the screen). The display is updated dynamically, and in vt100 supported settings, produces a rough appearance of animation. The header information for Program 11.10 is placed in the file local_TSD.h . The contents of local_TSD.h are shown in Figure 11.17.

[22] I am aware that using vt100 emulationescape codes will limit the range of platforms on which the example may run. However, as vt100 emulation is fairly ubiquitous, the actual number of platforms excluded should be minimal.

Figure 11.17 The local_TSD.h file for Program 11.10.

File : local_TSD.h /* local header file for example p11.10.cxx */ #define _REENTRANT #define _GNU_SOURCE + #include #include #include #include #include 10 #include #include #include #include #include + #include using namespace std; const int ROW=20, COL=42, MAX=10; const char ESC='3'; inline 20 void LOCATE(int row, int col){ cout << ESC << "[" << row << ";" << col << "H"; } inline void CLS( ){ + LOCATE(1, 1); cout << ESC << "[2J"; } int my_rand(int start, int range){ 30 struct timeval t; gettimeofday(&t, (struct timezone *)NULL); return (int)(start+((float)range * rand_r((unsigned *)&t.tv_usec)) / (RAND_MAX+1.0)); } + typedef struct { int left, right, top, bot; } DIRECTION; static char guys[] = "ABCDEFGHIJ"; int n_dead = 0; 40 char *the_file; pthread_mutex_t scrn_lock; // function prototypes void display_screen(char *); bool boxed(char *, int, int); + void move(char *, int *, int *, char); void neighbors( int , int , DIRECTION * ); void *play( void * );

Along with the vt100 functions, the local_TSD.h file contains a type definition for a DIRECTION structure that will hold the indices of neighbor locations. Within the program the two-dimensional grid is treated as a vector. Five user-defined functions are prototyped. The display_screen function, passed a reference to the grid, displays the current contents of the grid at a set location on the screen. The boxed function, passed a reference to the grid and a row and column location, returns a true if the neighbors of the rowcolumn location are all occupied; otherwise, it returns false. The move function finds and moves to a new location. The move function is passed a reference to the grid and the rowcolumn location, as well as a copy of the current letter associated with a given thread. Upon completion of move , the grid and the rowcolumn location are updated. The neighbors function is passed a copy of a rowcolumn location and returns a DIRECTION structure containing the indices of the neighbor locations. The play function, passed to each thread, serves as a driver routine for the activities of the thread.

Program 11.10 Animating threads.

File : p11.10.cxx /* p11.10.cxx: Thread animation Compile : g++ -o p11.10 p11.10.cxx -lpthread */ + #include "local_TSD.h" int main(int argc, char *argv[]) { char the_screen[ROW][COL]; pthread_t thread_id[MAX]; 10 int fd0, n_threads; struct stat buf; if (argc != 3) { // check cmd line cerr << "Usage " << *argv << " file_name #_threads" << endl; return 1; + } if ((n_threads = atoi(argv[2])) > MAX ) { cerr << "# threads must be < " << MAX+1 << endl; return 2; } 20 setbuf(stdout, NULL); guys[n_threads] = '

File : p11.10.cxx /* p11.10.cxx: Thread animation Compile : g++ -o p11.10 p11.10.cxx -lpthread */ + #include "local_TSD.h" int main(int argc, char *argv[]) { char the_screen[ROW][COL]; pthread_t thread_id[MAX]; 10 int fd0, n_threads; struct stat buf; if (argc != 3) { // check cmd line cerr << "Usage " << *argv << " file_name #_threads" << endl; return 1; + } if ((n_threads = atoi(argv[2])) > MAX ) { cerr << "# threads must be < " << MAX+1 << endl; return 2; } 20 setbuf ( stdout , NULL); guys[n_threads] = ''; memset (the_screen, ' ', sizeof(the_screen)); // clear screen array // open file for mapping if ((fd0 = open(argv[1], O_CREAT O_RDWR, 0666)) < 0) { + cerr << " Open error on file " << argv[1] << endl; return 3; } // write screen to file write(fd0, the_screen, sizeof(the_screen)); if (fstat(fd0, &buf) < 0) { // stats on mapped file 30 cerr << " fstat error on file " << the_file << endl; return 4; } // establish the mapping if ((the_file =(char *) mmap(0, (size_t) buf.st_size, PROT_READ PROT_WRITE, MAP_SHARED, fd0, 0)) == NULL) { + cerr << "mmap failure" << endl; return 5; } CLS( ); // clear the display for (int i=0; i < n_threads; ++i) { // generate the threads 40 pthread_create( &thread_id[i], NULL, play, (void *)i); } do { // in main thread sleep(1); // pause a bit pthread_mutex_lock( &scrn_lock); + display_screen(the_file); // display screen pthread_mutex_unlock( &scrn_lock); } while (n_dead < n_threads); // while threads left for(int i=0; i < n_threads; ++i) pthread_join( thread_id[i], (void **) NULL); 50 LOCATE(25, 1); close(fd0); return 0; } /* + Play the game by moving a character around the grid */ void * play( void *numb ){ static pthread_mutex_t the_lock; // single copy of these 60 static pthread_key_t the_key; static int first_time = 1; int row, col; // local to each invocation char pch; void *my_let = NULL; // thread specific data + if ( first_time ) { pthread_mutex_lock( &the_lock ); if ( first_time ) { pthread_key_create( &the_key, NULL ); first_time = 0; 70 } pthread_mutex_unlock( &the_lock ); } if ( (my_let = pthread_getspecific( the_key )) == NULL ) { my_let = (int *) &numb; + pthread_setspecific( the_key, my_let ); // associate with key } row=my_rand(1,ROW)-1; // start at random location col=my_rand(1,COL)-1; pch = (char) (65+*(int *)pthread_getspecific(the_key)); 80 do { move(the_file, &row, &col, pch); // move around } while( !boxed( the_file, row, col)); // while not boxed in n_dead++; // update terminated threads guys[*(int *)pthread_getspecific(the_key)] = '*'; + pthread_mutex_lock( &scrn_lock ); LOCATE(1, 1); cout << "Dead = " << n_dead << "[" << guys << "]"; pthread_mutex_unlock( &scrn_lock ); return NULL; 90 } /* Find and move to new location. */ void + move(char *s, int *r, int *c, char pch) { int old_offset = (*r * COL + *c), new_offset = -1; DIRECTION d; neighbors( *r, *c, &d ); // get neighbor locations 100 do { if ( my_rand(1,3) == 1 ) sleep(1); // 1/3 time sleep first switch ( my_rand(1,4) ) { case 1: if ( *(s + d.left ) == ' ' ) new_offset = d.left; + break; case 2: if ( *(s + d.right ) == ' ' ) new_offset = d.right; break; case 3: 110 if ( *(s + d.top ) == ' ' ) new_offset = d.top; break; case 4: if ( *(s + d.bot ) == ' ' ) new_offset = d.bot; break; + } } while( new_offset == -1 ); *r = new_offset / COL; *c = new_offset % COL; *(s + new_offset) = pch; // assign new location 120 if ( *(s + old_offset) != '@' ) // if its not a start loc *(s + old_offset) += 32; // change old loc to LC } /* Display the screen using VT100 escape codes for cursor placement. + */ void display_screen(char *s) { static int pass = 1; static char buffer[COL + 1]; 130 LOCATE(1, 33); cout << "Thread World"; LOCATE(2, 18); cout << "+------------------------------------------+"; for (int i=3; i < 23; ++i) { + LOCATE(i, 18); // move to screen location strncpy (buffer, (s + (i - 3) * COL), COL); // get output segment cout << "" << buffer << ""; } LOCATE(23, 18); 140 cout << "+------------------------------------------+"; LOCATE(24, 20); cout << "Pass " << ++pass; } /* + Check neighbors to see if any free locations are left */ bool boxed(char *s, int r, int c) { DIRECTION d; 150 neighbors( r, c, &d ); // get my neighbors return ( *(s+d.left) != ' ' && *(s+d.right) != ' ' && *(s+d.bot ) != ' ' && *(s+d.top ) != ' '); } /* + Calculate the surrounding locations */ void neighbors( int row, int col, DIRECTION *d ){ d->left = row * COL + (col > 0 ? col - 1 : COL - 1); 160 d->right = row * COL + (col > COL - 2 ? 0 : col + 1); d->top = (row > 0 ? row - 1 : ROW - 1) * COL + col; d->bot = (row > ROW - 2 ? 0 : row + 1) * COL + col; }

'; memset(the_screen, ' ', sizeof(the_screen)); // clear screen array // open file for mapping if ((fd0 = open(argv[1], O_CREAT O_RDWR, 0666)) < 0) { + cerr << "Open error on file " << argv[1] << endl; return 3; } // write screen to file write(fd0, the_screen, sizeof(the_screen)); if (fstat(fd0, &buf) < 0) { // stats on mapped file 30 cerr << "fstat error on file " << the_file << endl; return 4; } // establish the mapping if ((the_file =(char *) mmap(0, (size_t) buf.st_size, PROT_READ PROT_WRITE, MAP_SHARED, fd0, 0)) == NULL) { + cerr << "mmap failure" << endl; return 5; } CLS( ); // clear the display for (int i=0; i < n_threads; ++i) { // generate the threads 40 pthread_create( &thread_id[i], NULL, play, (void *)i); } do { // in main thread sleep(1); // pause a bit pthread_mutex_lock( &scrn_lock); + display_screen(the_file); // display screen pthread_mutex_unlock( &scrn_lock); } while (n_dead < n_threads); // while threads left for(int i=0; i < n_threads; ++i) pthread_join( thread_id[i], (void **) NULL); 50 LOCATE(25, 1); close(fd0); return 0; } /* + Play the game by moving a character around the grid */ void * play( void *numb ){ static pthread_mutex_t the_lock; // single copy of these 60 static pthread_key_t the_key; static int first_time = 1; int row, col; // local to each invocation char pch; void *my_let = NULL; // thread specific data + if ( first_time ) { pthread_mutex_lock( &the_lock ); if ( first_time ) { pthread_key_create( &the_key, NULL ); first_time = 0; 70 } pthread_mutex_unlock( &the_lock ); } if ( (my_let = pthread_getspecific( the_key )) == NULL ) { my_let = (int *) &numb; + pthread_setspecific( the_key, my_let ); // associate with key } row=my_rand(1,ROW)-1; // start at random location col=my_rand(1,COL)-1; pch = (char) (65+*(int *)pthread_getspecific(the_key)); 80 do { move(the_file, &row, &col, pch); // move around } while( !boxed( the_file, row, col)); // while not boxed in n_dead++; // update terminated threads guys[*(int *)pthread_getspecific(the_key)] = '*'; + pthread_mutex_lock( &scrn_lock ); LOCATE(1, 1); cout << "Dead = " << n_dead << "[" << guys << "]"; pthread_mutex_unlock( &scrn_lock ); return NULL; 90 } /* Find and move to new location. */ void + move(char *s, int *r, int *c, char pch) { int old_offset = (*r * COL + *c), new_offset = -1; DIRECTION d; neighbors( *r, *c, &d ); // get neighbor locations 100 do { if ( my_rand(1,3) == 1 ) sleep(1); // 1/3 time sleep first switch ( my_rand(1,4) ) { case 1: if ( *(s + d.left ) == ' ' ) new_offset = d.left; + break; case 2: if ( *(s + d.right ) == ' ' ) new_offset = d.right; break; case 3: 110 if ( *(s + d.top ) == ' ' ) new_offset = d.top; break; case 4: if ( *(s + d.bot ) == ' ' ) new_offset = d.bot; break; + } } while( new_offset == -1 ); *r = new_offset / COL; *c = new_offset % COL; *(s + new_offset) = pch; // assign new location 120 if ( *(s + old_offset) != '@' ) // if its not a start loc *(s + old_offset) += 32; // change old loc to LC } /* Display the screen using VT100 escape codes for cursor placement. + */ void display_screen(char *s) { static int pass = 1; static char buffer[COL + 1]; 130 LOCATE(1, 33); cout << "Thread World"; LOCATE(2, 18); cout << "+------------------------------------------+"; for (int i=3; i < 23; ++i) { + LOCATE(i, 18); // move to screen location strncpy(buffer, (s + (i - 3) * COL), COL); // get output segment cout << "" << buffer << ""; } LOCATE(23, 18); 140 cout << "+------------------------------------------+"; LOCATE(24, 20); cout << "Pass " << ++pass; } /* + Check neighbors to see if any free locations are left */ bool boxed(char *s, int r, int c) { DIRECTION d; 150 neighbors( r, c, &d ); // get my neighbors return ( *(s+d.left) != ' ' && *(s+d.right) != ' ' && *(s+d.bot ) != ' ' && *(s+d.top ) != ' '); } /* + Calculate the surrounding locations */ void neighbors( int row, int col, DIRECTION *d ){ d->left = row * COL + (col > 0 ? col - 1 : COL - 1); 160 d->right = row * COL + (col > COL - 2 ? 0 : col + 1); d->top = (row > 0 ? row - 1 : ROW - 1) * COL + col; d->bot = (row > ROW - 2 ? 0 : row + 1) * COL + col; }

In main , a character array, the_screen , is allocated along with an array to hold the thread IDs. The command-line contents are examined. The user must pass the name of a file and the number of threads to be generated. The number of threads, stored as n_threads , is used as an index to place a NULL in the guys array. The guys array is a list of uppercase letters that represent the active threads. A series of statements are used to map the the_screen display to the file name passed on the command line. While mapping the display to a file is not integral to the overall functionality of the example, it does show how this can be done and would allow, should the user so desire , a means for archiving the program's output. A loop is used to create the threads, and each is passed a reference to the user-defined function play . The initial thread, executing main , then loops until all threads have finished their activities. As it loops , after each one-second sleep , the screen is redisplayed.

The user-definded play function allocates the key . The logic to accomplish this is a variation of our previous example. The TSD *my_let reference, local to each invocation of play , is associated with the key by checking the return from the pthread_getspecific library function. If a NULL value is returned, as in the initial pass-through, the loop value from main (passed in as numb ) is assigned to my_let and associated with the key via a call to pthread_setspecific .

A random location is chosen as the starting point for the thread. The character to be displayed is retrieved with a call to pthread_getspecific . Using a do-while loop, the thread moves about the grid while it is not boxed in. Once boxed in, the thread exits the activity loop. It then increments a global value representing the number of inactive ( expired ) threads and marks the appropriate location in the guys array with an * . Finally, it updates the display of terminated threads and exits.

As written, this example usually produces rather entertaining output. However, as the program contains some oversights in logic, it will on occasion not work as advertised. [23]

[23] The oversights are the basis for the exercises associated with this program.

Figure 11.18 contains an output display of a partial run of Program 11.10 with five competing threads (letters AE).

Figure 11.18. Partial output from Program 11.10.

In the run shown, five threads were generated. Thread D was terminated when it became boxed in (in this case surrounded by its own previous movements shown by the lowercase d 's). The upper left corner of the output shown has an * in place of the letter D (indicating its termination). As shown, thread C has also become boxed in. However, either the program has somehow missed this fact (the previously mentioned oversight) or it was interrupted before it had a chance to fully update the screen. The remaining threads are active. As shown, the thread executing main had completed its 15th pass.

EXERCISE

If we run Program 11.10 several times, we should occasionally notice the program does not terminate properly. When this happens, the program appears to believe one or more threads are still active, even though the display clearly indicates that they are boxed in. Find, and correct, the source of this "problem."

EXERCISE

The logic for the user-defined move function is simplistic. Modify Program 11.10 to incorporate two (or more) different move functions. These new move function(s), each run by a separate thread, should attempt to move in such a manner as to keep the thread from being boxed in for as long as possible. Where practical, they should also attempt to block in (thus terminate) other threads. Gather data to show your move function is superior to the initial simplistic version. If time permits , try pitting your move function against the move functions written by others. If you do not want to share your source for the function, just supply the unique move function name for its invocation in main and link the object code for your move function at compile time.

EXERCISE

Using the code in Program 11.10 as a base, write a threaded version of Conway's Game of Life, whereby each thread manages a starting configuration of cells . Establish your own rules for what happens when competing threads attempt to access the same cell location.

Категории