Chinaunix首页 | 论坛 | 博客
  • 博客访问: 364013
  • 博文数量: 78
  • 博客积分: 2222
  • 博客等级: 大尉
  • 技术积分: 745
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-24 10:48
文章分类

全部博文(78)

文章存档

2012年(7)

2011年(33)

2010年(38)

分类: LINUX

2010-11-30 16:06:42

Mutex Variables

Overview

  • Mutex is an abbreviation for "mutual exclusion". Mutex variables are one of the primary means of implementing thread synchronization and for protecting shared data when multiple writes occur.

  • A mutex variable acts like a "lock" protecting access to a shared data resource. The basic concept of a mutex as used in Pthreads is that only one thread can lock (or own) a mutex variable at any given time. Thus, even if several threads try to lock a mutex only one thread will be successful. No other thread can own that mutex until the owning thread unlocks that mutex. Threads must "take turns" accessing protected data.

  • Mutexes can be used to prevent "race" conditions. An example of a race condition involving a bank transaction is shown below:

    Thread 1Thread 2Balance
    Read balance: $1000 $1000
     Read balance: $1000$1000
     Deposit $200$1000
    Deposit $200 $1000
    Update balance $1000+$200 $1200
     Update balance $1000+$200$1200

  • In the above example, a mutex should be used to lock the "Balance" while a thread is using this shared data resource.

  • Very often the action performed by a thread owning a mutex is the updating of global variables. This is a safe way to ensure that when several threads update the same variable, the final value is the same as what it would be if only one thread performed the update. The variables being updated belong to a "critical section".

  • A typical sequence in the use of a mutex is as follows:
    • Create and initialize a mutex variable
    • Several threads attempt to lock the mutex
    • Only one succeeds and that thread owns the mutex
    • The owner thread performs some set of actions
    • The owner unlocks the mutex
    • Another thread acquires the mutex and repeats the process
    • Finally the mutex is destroyed

  • When several threads compete for a mutex, the losers block at that call - an unblocking call is available with "trylock" instead of the "lock" call.

  • When protecting shared data, it is the programmer's responsibility to make sure every thread that needs to use a mutex does so. For example, if 4 threads are updating the same data, but only one uses a mutex, the data can still be corrupted.
Mutex Variables

Creating and Destroying Mutexes

 Routines:

     (mutex,attr)

     (mutex)

     (attr)

     (attr)

 Usage:

  • Mutex variables must be declared with type pthread_mutex_t, and must be initialized before they can be used. There are two ways to initialize a mutex variable:

    1. Statically, when it is declared. For example: 
      pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;

    2. Dynamically, with the pthread_mutex_init() routine. This method permits setting mutex object attributes, attr.

    The mutex is initially unlocked.

  • The attr object is used to establish properties for the mutex object, and must be of type pthread_mutexattr_t if used (may be specified as NULL to accept defaults). The Pthreads standard defines three optional mutex attributes:
    • Protocol: Specifies the protocol used to prevent priority inversions for a mutex.
    • Prioceiling: Specifies the priority ceiling of a mutex.
    • Process-shared: Specifies the process sharing of a mutex.

    Note that not all implementations may provide the three optional mutex attributes.

  • The pthread_mutexattr_init() and pthread_mutexattr_destroy() routines are used to create and destroy mutex attribute objects respectively.

  • pthread_mutex_destroy() should be used to free a mutex object which is no longer needed.
Mutex Variables

Locking and Unlocking Mutexes

 Routines:

     (mutex)

     (mutex)

     (mutex)

 Usage:

  • The pthread_mutex_lock() routine is used by a thread to acquire a lock on the specified mutex variable. If the mutex is already locked by another thread, this call will block the calling thread until the mutex is unlocked.

  • pthread_mutex_trylock() will attempt to lock a mutex. However, if the mutex is already locked, the routine will return immediately with a "busy" error code. This routine may be useful in preventing deadlock conditions, as in a priority-inversion situation.

  • pthread_mutex_unlock() will unlock a mutex if called by the owning thread. Calling this routine is required after a thread has completed its use of protected data if other threads are to acquire the mutex for their work with the protected data. An error will be returned if:
    • If the mutex was already unlocked
    • If the mutex is owned by another thread

  • There is nothing "magical" about mutexes...in fact they are akin to a "gentlemen's agreement" between participating threads. It is up to the code writer to insure that the necessary threads all make the the mutex lock and unlock calls correctly. The following scenario demonstrates a logical error:
        Thread 1     Thread 2     Thread 3
        Lock         Lock         
        A = 2        A = A+1      A = A*B
        Unlock       Unlock    
    
Question: When more than one thread is waiting for a locked mutex, which thread will be granted the lock first after it is released? 


Example: Using Mutexes

     Example Code - Using Mutexes
      This example program illustrates the use of mutex variables in a threads program that performs a dot product. The main data is made available to all threads through a globally accessible structure. Each thread works on a different part of the data. The main thread waits for all the threads to complete their computations, and then it prints the resulting sum.

    #include 
    #include 
    #include 
    
    /*   
    The following structure contains the necessary information  
    to allow the function "dotprod" to access its input data and 
    place its output into the structure.  
    */
    
    typedef struct 
     {
       double      *a;
       double      *b;
       double     sum; 
       int     veclen; 
     } DOTDATA;
    
    /* Define globally accessible variables and a mutex */
    
    #define NUMTHRDS 4
    #define VECLEN 100
       DOTDATA dotstr; 
       pthread_t callThd[NUMTHRDS];
       pthread_mutex_t mutexsum;
    
    /*
    The function dotprod is activated when the thread is created.
    All input to this routine is obtained from a structure 
    of type DOTDATA and all output from this function is written into
    this structure. The benefit of this approach is apparent for the 
    multi-threaded program: when a thread is created we pass a single
    argument to the activated function - typically this argument
    is a thread number. All  the other information required by the 
    function is accessed from the globally accessible structure. 
    */
    
    void *dotprod(void *arg)
    {
    
       /* Define and use local variables for convenience */
    
       int i, start, end, len ;
       long offset;
       double mysum, *x, *y;
       offset = (long)arg;
         
       len = dotstr.veclen;
       start = offset*len;
       end   = start + len;
       x = dotstr.a;
       y = dotstr.b;
    
       /*
       Perform the dot product and assign result
       to the appropriate variable in the structure. 
       */
    
       mysum = 0;
       for (i=start; ipthread_mutex_lock (&mutexsum);
       dotstr.sum += mysum;
       pthread_mutex_unlock (&mutexsum);
    
       pthread_exit((void*) 0);
    }
    
    /* 
    The main program creates threads which do all the work and then 
    print out result upon completion. Before creating the threads,
    the input data is created. Since all threads update a shared structure, 
    we need a mutex for mutual exclusion. The main thread needs to wait for
    all threads to complete, it waits for each one of the threads. We specify
    a thread attribute value that allow the main thread to join with the
    threads it creates. Note also that we free up handles when they are
    no longer needed.
    */
    
    int main (int argc, char *argv[])
    {
       long i;
       double *a, *b;
       void *status;
       pthread_attr_t attr;
    
       /* Assign storage and initialize values */
       a = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));
       b = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));
      
       for (i=0; ipthread_mutex_init(&mutexsum, NULL);
             
       /* Create threads to perform the dotproduct  */
       pthread_attr_init(&attr);
       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    
    	for(i=0; ipthread_create(&callThd[i], &attr, dotprod, (void *)i);
    	}
    
     	pthread_attr_destroy(&attr);
    
            /* Wait on the other threads */
    	for(i=0; ipthread_join(callThd[i], &status);
    	}
    
       /* After joining, print out the results and cleanup */
       printf ("Sum =  %f \n", dotstr.sum);
       free (a);
       free (b);
       pthread_mutex_destroy(&mutexsum);
       pthread_exit(NULL);
    }   
    
    View source code Serial version 
    View source code Pthreads version

    Condition Variables

    Overview

       

    • Condition variables provide yet another way for threads to synchronize. While mutexes implement synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data.

       

    • Without condition variables, the programmer would need to have threads continually polling (possibly in a critical section), to check if the condition is met. This can be very resource consuming since the thread would be continuously busy in this activity. A condition variable is a way to achieve the same goal without polling.

       

    • A condition variable is always used in conjunction with a mutex lock.

       

    • A representative sequence for using condition variables is shown below.

      Main Thread
      • Declare and initialize global data/variables which require synchronization (such as "count")
      • Declare and initialize a condition variable object
      • Declare and initialize an associated mutex
      • Create threads A and B to do work
      Thread A
      • Do work up to the point where a certain condition must occur (such as "count" must reach a specified value)
      • Lock associated mutex and check value of a global variable
      • Call pthread_cond_wait() to perform a blocking wait for signal from Thread-B. Note that a call to pthread_cond_wait() automatically and atomically unlocks the associated mutex variable so that it can be used by Thread-B.
      • When signalled, wake up. Mutex is automatically and atomically locked.
      • Explicitly unlock mutex
      • Continue
      Thread B
      • Do work
      • Lock associated mutex
      • Change the value of the global variable that Thread-A is waiting upon.
      • Check value of the global Thread-A wait variable. If it fulfills the desired condition, signal Thread-A.
      • Unlock mutex.
      • Continue
      Main Thread
        Join / Continue

    Condition Variables

    Creating and Destroying Condition Variables

     Routines:

       (condition,attr)

       (condition)

       (attr)

       (attr)

     Usage:

       

    • Condition variables must be declared with type pthread_cond_t, and must be initialized before they can be used. There are two ways to initialize a condition variable:

         

      1. Statically, when it is declared. For example:
        pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;

         

      2. Dynamically, with the pthread_cond_init() routine. The ID of the created condition variable is returned to the calling thread through the condition parameter. This method permits setting condition variable object attributes, attr.

       

    • The optional attr object is used to set condition variable attributes. There is only one attribute defined for condition variables: process-shared, which allows the condition variable to be seen by threads in other processes. The attribute object, if used, must be of type pthread_condattr_t (may be specified as NULL to accept defaults).

      Note that not all implementations may provide the process-shared attribute.

       

    • The pthread_condattr_init() and pthread_condattr_destroy() routines are used to create and destroy condition variable attribute objects.

       

    • pthread_cond_destroy() should be used to free a condition variable that is no longer needed.

    Condition Variables

    Waiting and Signaling on Condition Variables

     Routines:

       (condition,mutex)

       (condition)

       (condition)

     Usage:

       

    • pthread_cond_wait() blocks the calling thread until the specified condition is signalled. This routine should be called while mutex is locked, and it will automatically release the mutex while it waits. After signal is received and thread is awakened, mutex will be automatically locked for use by the thread. The programmer is then responsible for unlocking mutex when the thread is finished with it.

       

    • The pthread_cond_signal() routine is used to signal (or wake up) another thread which is waiting on the condition variable. It should be called after mutex is locked, and must unlock mutex in order for pthread_cond_wait() routine to complete.

       

    • The pthread_cond_broadcast() routine should be used instead of pthread_cond_signal() if more than one thread is in a blocking wait state.

       

    • It is a logical error to call pthread_cond_signal() before calling pthread_cond_wait().

       

    Proper locking and unlocking of the associated mutex variable is essential when using these routines. For example:
    • Failing to lock the mutex before calling pthread_cond_wait() may cause it NOT to block.

       

    • Failing to unlock the mutex after calling pthread_cond_signal() may not allow a matching pthread_cond_wait() routine to complete (it will remain blocked).

     


     

     

    Example: Using Condition Variables

       Example Code - Using Condition Variables
        This simple example code demonstrates the use of several Pthread condition variable routines. The main routine creates three threads. Two of the threads perform work and update a "count" variable. The third thread waits until the count variable reaches a specified value.

      #include 
      #include 
      #include 
      
      #define NUM_THREADS 3
      #define TCOUNT 10
      #define COUNT_LIMIT 12
      
      int count = 0;
      int thread_ids[3] = {0,1,2};
      pthread_mutex_t count_mutex;
      pthread_cond_t count_threshold_cv;
      
      void *inc_count(void *t) 
      {
       int j,i;
       double result=0.0;
       long my_id = (long)t;
      
       for (i=0; ipthread_mutex_lock(&count_mutex);
       count++;
      
       /* 
       Check the value of count and signal waiting thread when condition is
       reached. Note that this occurs while mutex is locked. 
       */
       if (count == COUNT_LIMIT) {
       pthread_cond_signal(&count_threshold_cv);
       printf("inc_count(): thread %ld, count = %d Threshold reached.\n", 
       *my_id, count);
       }
       printf("inc_count(): thread %ld, count = %d, unlocking mutex\n", 
      	*my_id, count);
       pthread_mutex_unlock(&count_mutex);
      
       /* Do some "work" so threads can alternate on mutex lock */
       sleep(1);
       }
       pthread_exit(NULL);
      }
      
      void *watch_count(void *t) 
      {
       long my_id - (long)t;
      
       printf("Starting watch_count(): thread %ld\n", *my_id);
      
       /*
       Lock mutex and wait for signal. Note that the pthread_cond_wait 
       routine will automatically and atomically unlock mutex while it waits. 
       Also, note that if COUNT_LIMIT is reached before this routine is run by
       the waiting thread, the loop will be skipped to prevent pthread_cond_wait
       from never returning. 
       */
       pthread_mutex_lock(&count_mutex);
       if (countpthread_cond_wait(&count_threshold_cv, &count_mutex);
       printf("watch_count(): thread %ld Condition signal received.\n", my_id);
       count += 125;
       printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
       }
       pthread_mutex_unlock(&count_mutex);
       pthread_exit(NULL);
      }
      
      int main (int argc, char *argv[])
      {
       int i, rc;
       long t1=1, t2=2, t3=3;
       pthread_t threads[3];
       pthread_attr_t attr;
      
       /* Initialize mutex and condition variable objects */
       pthread_mutex_init(&count_mutex, NULL);
       pthread_cond_init (&count_threshold_cv, NULL);
      
       /* For portability, explicitly create threads in a joinable state */
       pthread_attr_init(&attr);
       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
       pthread_create(&threads[0], &attr, watch_count, (void *)t1);
       pthread_create(&threads[1], &attr, inc_count, (void *)t2);
       pthread_create(&threads[2], &attr, inc_count, (void *)t3);
      
       /* Wait for all threads to complete */
       for (i=0; ipthread_join(threads[i], NULL);
       }
       printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS);
      
       /* Clean up and exit */
       pthread_attr_destroy(&attr);
       pthread_mutex_destroy(&count_mutex);
       pthread_cond_destroy(&count_threshold_cv);
       pthread_exit(NULL);
      
      }
      
      View source code View sample output

阅读(1757) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~