820 likes | 1.18k Views
Unix Synchronization * Mutexes * Semaphores - named - unnamed. First we will study thread synchronization in Posix. Posix thread synchronization is done with mutex locks. First, a quick review of threads. operating systems. Consider the following code.
E N D
Unix Synchronization * Mutexes * Semaphores - named - unnamed
First we will study thread synchronization in Posix Posix thread synchronization is done with mutex locks
operating systems Consider the following code #include <stdio.h> void print_msg(char*); int main ( ) { print_msg(“hello “); print_msg(“world\n”); return 0; } void print_msg(char* m) { int i; for (i = 0; i < 5; i++) { printf(“%s”, m); fflush( stdout ); sleep(1); } }
operating systems This program will produce hello hello hello hello hello world world world world world
operating systems The thread of execution is print_msg( ) main( )
operating systems With multiple threads of execution print_msg( ) main( )
void print_msg(void* m) { char* cp = (char*) m; int i; for (i = 0; i < 5; i++) { printf(“%s”, cp); fflush( stdout ); sleep(1); } } Consider the following code operating systems #include <stdio.h> #include <pthread.h> void *print_msg(void *); int main ( ) { pthread thr1, thr2; pthread_create(&thr1, NULL, print_msg, (void *)”hello “); pthread_create(&thr2, NULL, print_msg, (void *)”world\n “); pthread_join(thr1, NULL); pthread_join(thr2, NULL); return 0; }
operating systems This program will produce hello world hello world hello world hello world hello world
operating systems the address of the thread the function to run on the thread pthread_create(&thr1, NULL, print_msg, (void *)”hello “); use default thread attributes start a new thread of execution. the function parameter
operating systems the name of the thread pthread_join(thr1, NULL); where to store any return value wait here for this thread to finish
operating systems Thread Communication One of the advantages of threads is that they share the same address space. Threads can communicate by reading and writing to that shared space.
operating systems static int globalValue = 0; int main ( ) { int i; pthread_t mythread; pthread_create(&mythread, NULL, funny_add, NULL); for (i = 0; i < 10; i++) { globalValue++; sleep(1); } pthread_join(mythread, NULL); printf(“\nDone ...global value = %d\n”, globalValue); return 0; } our shared variable
void *funny_add(void *arg) { int j; for (j = 0; j < 10; j++) { int temp = globalValue + 1; sleep(1); globalValue = temp; } }
operating systems Using a shared variable funny_add( ) main( ) pthread_create temp = globalValue + 1 sleep globalValue = temp globalValue++ sleep pthread_join globalValue
operating systems This program seems to work okay, but it has a potential problem .... what is it? There is no thread synchronization. We cannot guarantee the order in which the threads are executed. Let’s Try It – exampleOne_noprint.c
Hmmmm.... Let’s add some print statements so that we can see how the threads are executing. exampleOne_nosync.c
static int globalValue = 0; int main ( ) { int i; pthread_t mythread; pthread_create(&mythread, NULL, funny_add, NULL); for (i = 0; i < 10; i++) { fprintf(stderr, “Main thread, globalValue = %s\n”, globalValue); globalValue++; fprintf(stderr, “Main thread, incr globalValue = %s\n”, globalValue); sleep(1); } pthread_join(mythread, NULL); printf(“\nDone ...global value = %d\n”, globalValue); return 0; } our shared variable operating systems
void *funny_add(void *arg) { int j; for (j = 0; j < 10; j++) { fprintf(stderr, “in funny_add, globalValue = %d\n”, globalValue); int temp = globalValue + 1; sleep(1); globalValue = temp; fprintf(stderr, “in funny_add, incr globalValue = %d\n”, globalValue); } }
Mutexes A mutex is a special variable that can either be in a locked state or an unlocked state. If a mutex is locked, it has a distinguished thread that holds or owns the mutex. If no thread holds the mutex, it is unlocked, free, or available. A mutex also has a queue for threads that are waiting to hold the mutex. The order in which the threads in the queue obtain the mutex is determined by the OS’s thread scheduling policy.
The mutex is the simplest and most efficient thread synchronization mechanism. A mutex is meant to be held for short periods of time. A thread that is waiting for a mutex is not logically interruptible, except by termination of the process or termination of the thread. Mutex locks are ideal for making changes to data structures in which the state of the data structure is temporarily inconsistent.
using the static initializer PTHREAD_MUTEX_INITIALIZER this method is most efficient, and is guaranteed to only be executed exactly once before any threads begin execution. Use it for statically allocated mutex variables. calling pthread_mutex_init ( ) for dynamically allocated mutex variables. pthread_mutex_init (&my_lock, NULL); Creating an Initializing a Mutex In posix mutexes are declared with the data type pthread_mutex_t. A program must always initialize a mutex before it is used. A program can initialize a mutex in one of two ways:
Dynamic Mutex Initialization returns 0 if successful int pthread_mutex_init(pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr); pointer to a dynamically allocated region of memory to store the mutex. pointer to mutex attributes okay to pass NULL when you initialize a mutex with pthead_mutex_init, you must destroy the mutex with pthread_mutex_destroy( ). Note that it does not free the memory used to store the mutex.
Static Mutex Initialization pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
Locking and Unlocking a Mutex int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); Blocks until the mutex is available All return 0 if successful Returns immediately if the mutex is not available Releases the mutex
static int globalValue = 0; static pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER; int main ( ) { int i; pthread_t mythread; pthread_create(&mythread, NULL, funny_add, NULL); for (i = 0; i < 10; i++) { pthread_mutex_lock(&mymutex); fprintf(stderr, “Main thread, globalValue = %s\n”, globalValue); globalValue++; fprintf(stderr, “Main thread, incr globalValue = %s\n”, globalValue); pthread_mutex_unlock(&mymutex); sleep(1); } pthread_join(mythread, NULL); printf(“\nDone ...global value = %d\n”, globalValue); return 0; } operating systems
operating systems Locking and unlocking are voluntary and a program only achieves mutual exclusion when it’s threads correctly acquire the appropriate mutex when entering a critical section of code, and release the mutex when finished. One way of ensuring this is to only allow access to shared variables through well defined functions. Then put the mutex lock and unlock calls in these functions. The locking mechanism is then transparent to the calling threads. A Volunteer Effort
operating systems Consider a program containing a bunch of threads that execute the instruction myGlobalValue = myGlobalValue +1; Since this statement will likely generate a single machine instruction, should we worry about using a mutex? Should I Always Use a Mutex? Yes, you should!
operating systems Why? Consider an SMP architecture that updates main memory in 32-bit blocks. Each processor may execute the instruction at the same time. The memory modification will then trickle down through level 1 and level 2 cache and then to main memory. If you are incrementing a 64 bit integer without mutexes, the uppermost 4 bytes might come from one processor and the lower 4 bytes from the other. Bummer!
operating systems How Many Mutexes If you place too many mutexes your code will slow down considerably. Use mutexes to serialize access to shared data. Don’t use them for local data. Don’t use them if your program logic ensures that only one thread can access shared data at a single time.
operating systems Protecting Unsafe Library Functions Many C library functions are not thread-safe. An example is the rand( ) function. However, you can safely use the rand( ) function in a multi-threaded environment if you can guarantee that no two threads are concurrently calling it.
operating systems int randsafe(double *ranp) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int error; if (error = pthread_mutex_lock(&lock)) return error; *ranp = (rand( ) +0.5) / (RAND_MAX + 1); return pthread_mutex_unlock(&lock); }
operating systems Suppose that we want to count the number of words in two separate files and add them together, using two threads to do the counting for us concurrently. Molay ch 14
operating systems Counting words with multiple threads of execution count_words( ) main( ) word_count++ word_count++ word_count file1 file2
operating systems Suppose that the code generated for word_count++; looks something like load register, word_count add register, 1 store register, word_count
time thread 1 operating systems 100 100 word_count load reg, word_count
time thread 1 thread 2 operating systems 100 word_count load reg, word_count 100 load reg, word_count add register, 1 101 store reg, word_count
time thread 1 thread 2 operating systems 100 word_count load reg, word_count 100 load reg, word_count 100 add register, 1 101 store reg, word_count
time Of course, this should now be 102! thread 1 thread 2 operating systems 101 word_count load reg, word_count 100 load reg, word_count 100 add register, 1 101 store reg, word_count add register, 1 101 store reg, word_count
time thread 1 thread 2 operating systems L O C K 100 word_count load reg, word_count 100 this thread gets blocked because the shared variable is locked Mutex Solution! add register, 1 101 U N L O C K 101 store reg, word_count now this thread can run --- and access the shared variable word_count word_count
operating systems You can solve this problem without using a mutex! Let each thread have its own counter. Add these counters at the end.
operating systems struct argset { char* fname; /* the file name */ int count; /* the word count */ };
pthread_t thr1; struct argset args1; args1.fname = argv[1]; args1.count = 0; . . . pthread_create(&thr1, NULL, count_words, (void *) &args1); . . . //print the sum of the two counters printf(“%5d total words\n”, args1.count + args2.count); pass in the struct as the argument
Now suppose your program is going to count the words in a very big file and a very small file. We would like to have some way for each thread to notify the main thread when it is done counting.
Using a Condition Variable we “signal” that there is something in the mailbox the mailbox has a lock on it to prevent two threads trying to access it at once.
Counting Thread 2 Counting Thread 1 Main Thread * set up a reporting mailbox - it has space for one count at a time - it has a flag that can be raised, it then snaps back - there is a mutex lock to protect the mailbox
Counting Thread 2 Counting Thread 1 Main Thread * unlock the mailbox and wait until the flag goes up
Counting Thread 2 Counting Thread 1 Main Thread * waits until it can lock the mailbox * if the mailbox is empty, it puts its count in the mailbox * It raises the flag on the mailbox * Then finally releases its lock
Counting Thread 2 Counting Thread 1 Main Thread * stops waiting when the flag goes up * locks the mailbox * takes out the count * processes the count * puts up the flag in case another count thread is waiting * unlocks the mailbox and waits