Chapter: Multicore Application Programming For Windows, Linux, and Oracle Solaris : Using POSIX Threads

Variables and Memory

Data can be shared either between threads or private to each thread. Examples of data that can be shared between threads are global variables and memory allocated on the heap.

Variables and Memory

 

Data can be shared either between threads or private to each thread. Examples of data that can be shared between threads are global variables and memory allocated on the heap. Listing 5.47 uses a global variable to hold the address of a region of memory allo-cated by a malloc()call. All the threads in an application would be able to access the global variable and therefore the allocated memory.

 

Listing 5.47   Sharing Memory Using Global Variables

#include <stdlib.h>

 

char * data;

 

int main()

 

{

 

data = (char*) malloc( 1024*1024 );

 

...

As previously discussed, care needs to be taken with data shared between threads. If the data is modified by other threads, it might be necessary to declare that data as volatile. For example, the code in Listing 5.48 would become an infinite loop if the variable done were not declared volatile.

 

Listing 5.48   Potential Infinite Loop

volatile int done = 0; void wait()

 

{

 

while ( !done ) {}

 

}

Alternative approaches might be to cast the variable done to be a volatile int, which would mean that the variable would be reloaded in the code only where this behavior was desirable, as shown in Listing 5.49. However, not all compilers will honor the cast to volatile.

Listing 5.49  Casting a Variable to Volatile

int done =0; void wait()

{

 

while ( !(volatile int)done ) {}

}

Alternatively, it is possible to use a function call to force the reloading of the variable, as shown in Listing 5.50. The reloading is necessary because the called function might modify the global variable.

 

Listing 5.50   Using a Function Call to Force the Reloading of a Variable

int done = 0;

 

void pause()

 

{

 

}

 

void wait()

 

{

 

while ( !done ) { pause(); }

}

Of course, some compiler optimizations such as inlining can cause code that relies on the side effects of function calls to enforce memory ordering to fail. Compilers often have directives or intrinsic functions that can be used to produce the desired behavior without risk of this breaking under inlining optimizations. This topic will be discussed in more detail in Chapter 8, “Hand-Coded Synchronization and Sharing.”

 

Much of this chapter has dealt with the methods available through the POSIX stan-dard that prevent multiple threads accessing the same data. But it is worth emphasizing that all accesses to shared data represent potential data races.

 

The other kind of data is thread-private data. Variables held on the stack are an example of thread-private data. Parameters passed into functions are another example. In the code shown in Listing 5.51, both variables a and b are private to a thread.

 

Listing 5.51   Two Thread-Private Variables

double func( double a )

 

{

 

double b;

...

However, it is sometimes useful to have “global” data that is private to a thread—data that is visible to all the routines that a thread executes but with the restriction that each thread sees only its own private copy of the data; this is known as thread-local storage.

 

There are two ways of allocating thread-local data. The first is to declare globally vari-ables with the __thread specifier. This prevents them being global variables and turns them into thread-local variables. In Listing 5.52, the variable mydata is local to the thread, so each thread can see a different value for the variable.

 

Listing 5.52   Thread-Local Data Declared Using the __thread Specifier

__thread void * mydata;

 

void * threadFunction( void * param )

 

{

 

mydata = param;

 

...

POSIX also provides a set of function calls for declaring and using thread-local variables. These function calls are not as convenient as the __thread specifier, because they use a key to identify each item of shared data. The key is created with a call to pthread_key_create(). When the key is no longer needed, it can be deleted by a call to pthread_key_delete(). All the threads can now use this key to get and set a thread-local parameter. The call pthread_setspecific() takes the key plus a value for the thread-local variable. A call to pthread_getspecific() will return the previously set value when the same key is passed in. Listing 5.53 shows the use of the thread-local storage routines.

 

Listing 5.53   Using POSIX Routines for Thread-Local Data

#include <pthread.h>

 

pthread_key_t parameter;

 

void * threadFunc( void * param )

 

{

 

pthread_setspecific( parameter, param );

 

...

 

void * param2 = pthread_getspecific( parameter );

 

}

 

int main()

 

{

 

pthread_t thread;

 

pthread_key_create( &parameter, 0 );

pthread_create( &thread, 0, threadFunc, 0 );

 

pthread_join( thread );

 

pthread_key_delete( parameter );

 

}

The pthread_key_create() call takes an optional destructor function. This func-tion is called when the thread terminates if the key still holds a value. This can be used to free any resources held by the thread. Listing 5.54 shows an example of this.

 

Listing 5.54   Using a Destructor Function with thread-local Storage

#include <pthread.h> #include <stdlib.h> #include <stdio.h>

 

pthread_key_t parameter;

 

void destructor( void * param )

 

{

 

free( param );

 

printf( "Memory freed\n" );

 

}

 

void * threadFunc( void * param )

 

{

 

char * mem = malloc( 100 ); pthread_setspecific( parameter, mem );

}

 

int main()

 

{

 

pthread_t thread;

 

pthread_key_create( &parameter, destructor );

 

pthread_create( &thread, 0, threadFunc, 0 );

 

pthread_join( thread, 0 );

 

pthread_key_delete( parameter );

}

One disadvantage of thread-local variables is that the global thread is unable to see them. So, in some cases, it is useful to use arrays to hold values produced by the child thread.


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail
Multicore Application Programming For Windows, Linux, and Oracle Solaris : Using POSIX Threads : Variables and Memory |


Privacy Policy, Terms and Conditions, DMCA Policy and Compliant

Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.