分享

Posix Thread (pthread) Programming

 astrotycoon 2015-01-27


Thread Synchronization

  • Thread Synchronization

    • The most important aspect of parallel programming is synchronization among the various parallel executing agents that access a common resource.

      Some access operations are conflicting and cannot be executed simulateneously

      One common conflicting operations are:

      • Reading by one thread and writing by another thread
      • Writing by one thread and writing by another thread

    • Recall that the global variables can be accessed by all threads

    • Recall that threads executes simultaneously, meaning the (assembler) instructions at multiple places in the program are executed (by multiple CPUs)

    • Consider the following program with 2 paralle threads of execution that is about to the same (global) variables:

      Suppose the variable "balance" contains the value 1000.

      "Normally", when both threads finish with their operations on the "balance" variable, the new values of "balance" will be 900.

      However, it is possible that the CPUs execute the assembler instructions interleaved.... (afterall, each CPU executes the instructions independently from one another).

      Hence, one possible execution ordering is the following:

             CPU 1                             CPU 2
       ---------------------            ------------------------
       move.l bal, D0   (D0 = 1000)
                                        move.l bal, D0   (D0 = 1000)
       add.l  #100, D0  (D0 = 1100)
                                        sub.l  #200, D0  (D0 = 800)
       move.l D0, bal   (bal = 1100)
                                        move.l D0, bal   (bal = 800)
      

      And when both threads finish with their access of the "balance" variable, the resulting value of balance is 800 !

    • Another execution order of the threads can produce the result balance = 1100.... (try to figure out how)

    • Obviously, there is a need to ensure that the parallel thread execution will not produce results that is different from the "normal" result.

    • This problem when parallel thread execution can produce abnormal results is called (thread) synchronization problem

  • Common synchronization methods

    From CS351, you may have learned the following common synchronization methods:

    • Mutex (or Mutually exclusive locks)
    • Read/Write Locks
    • Monitor
    • Binary semaphore
    • Counting semaphore
    • Conditional variable (probably not)...

  • Available synchronization methods in POSIX Threads:

    • Mutex (or Mutually exclusive locks)
    • Read-write lock
    • Conditional variable (a test-and-set operation)

    You can construct Binary semaphores and Counting semaphores using Conditional variables...

    So it is no big deal that PThreads do not implement them...



  • Mutex:

    • A mutex is an object (remember from CS170: variables + methods) with 2 (3 if you count creation as an operation) operations.

    • The operations on mutex objects are written with special assembler codes so that when one of the operations is invoked by some thread, no other threads can invoke any mutex operations until that thread finishes with the mutex call.

      (There are different way to achieve this "atomic" execution effect and will not discussed here. If you are interested, read more on "interrupt disable", "test-and-set" instructions)

  • Mutex operations:

    • Creation - initially, a mutex is always unlocked

    • lock - change a mutex from unlocked to locked. If the mutex was in a locked state, the call will cause the invoking thread to "block" (stop execution) until the mutex becomes unlocked

    • unlock - change a mutex from locked to unlocked. Only the thread that currently holding the lock can unlock a mutex.

  • PThreads Mutex API:

    • Defining a mutex variable "x":

         pthread_mutex_t x;   
      

    • Initializing a mutex variable:

         int pthread_mutex_init(pthread_mutex_t *mutex,
      			  const pthread_mutexattr_t *attr);
      
      Example:
      
         pthread_mutex_init(&x, NULL);    /* Default initialization */
      

    • Locking a mutex:

         int pthread_mutex_lock(pthread_mutex_t *mutex);   
      
      Example:
      
         pthread_mutex_lock(&x);
      

    • Unlocking a mutex:

         int pthread_mutex_unlock(pthread_mutex_t *mutex);
      
      Example:
      
         pthread_mutex_unlock(&x);
      

  • Using mutex to solve the variable access synchronization:

    A common usage of mutex is to synchronize updates to shared (global) variables

    Whenever a thread want to read or write a shared variable, it must enclose the operation by a "lock - unlock" pair.

    Example:

    The atomic execution of the mutex lock function prevents multiple threads from executing instructions that access the common variable.

  • NOTE:

    • Make sure you unlock a mutex after you are done with accessing the share variable(s).

      A common error is to forget the unlock call... the result is "deadlock" - you program hangs (no progress)

  • My Mutex Library call

    • Mutex is frequently to protect simultaneous access to variables

    • For my own convenience, I created a simpler interface for creating, locking and unlocking mutexes

    • Creating a (unlocked) mutex:

         void MutexInit(mutex **m)
         {
            *m = (mutex *) malloc(sizeof(mutex));
            pthread_mutex_init(&((*m)->mutex), NULL);    
         }
      
         Usage:    mutex *x;
      	     MutexInit(&x); 

    • Locking a mutex:

         void MutexLock(mutex *m)
         {
            if (m == NULL)
             { fprintf(stderr, "Err: Uninitialized mutex\n");    
               exit(1);
             }
            pthread_mutex_lock(&(m->mutex));
         }
      
         Usage:    MutexLock(x); 

    • UnLocking a mutex:

         void MutexUnLock(mutex *m)
         {
            if (m == NULL)
             { fprintf(stderr, "Err: Uninitialized mutex\n");    
               exit(1);
             }
            pthread_mutex_unlock(&(m->mutex));
         }
      
         Usage:    MutexUnLock(x); 

  • Example: use of Mutex to access a variable



  • Read/Write locks:

    • Read-write locks are very similar to mutexes, except that there are 2 types of locking operations: read-lock and write-lock.

    • A read-lock and write-lock operation conflict (i.e., both cannot be allowed to succeed)

    • Two write-lock operations also conflict

    • But 2 read-lock operations can be performed at the same time

    • Read/Write Lock API:

       int pthread_rwlock_init(pthread_rwlock_t *rwlock,
      			 const pthread_rwlockattr_t *attr);
       
      int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
      int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);



  • The Test-and-set operation

    The "test-and-set(x)" operation on a (memory) variable "x" performs the following operations atomically:

    • First, save the (original) value of "x" into an auxiliary register
    • Store the value 1 into "x"
    • Return the (original) auxiliary value of "x"

    The test-and-set operation is a powerful operation that can be use to implement all shared memory synchronization primitives.

  • Implementing mutex with test-and-set:

    • Just to give you an idea of the power of the test-and-set operation, here is an example showing how you can use the "test-and-set" operation to implement the same behavior of a "mutex" variable.

    • Mutex initialization:

       
      
         pthread_mutex_init(&x)   <---->   x = 0;    
         

    • Mutex lock:

       
      
         pthread_mutex_lock(&x)  <-->  
      
      		Loop: if ( test-and-set(x) == 1 )
      		         goto Loop;
         

    • Mutex unlock:

       
      
         pthread_mutex_unlock(&x)     <---->   x = 0;   
         

  • Conditional Variables

    • The conditional variable is the PThread's equivalent of the test-and-set operation

    • A conditional variable is initialized by using:

        int pthread_cond_init(pthread_cond_t *cond,
                              const pthread_condattr_t *attr);
      Example:
      
        pthread_cond_t x;
      
        pthread_cond_init(&x, NULL);
       

    • To implement the test-and-set operation, a conditional variable is always used in conjunction with a mutex variable.

    • There are 2 operations on conditional variables:

      • wait (wait for the condition to become true)
      • signal (signal that the condition has become true)

    • pthread_cond_wait(): used to block a thread on a condition variable (block means: put it to sleep, disallow it to run until further notice. If you recall from CS355 or CS351, you must realise that you would use a process queue to hold the "PCBs" of blocked threads)

         int pthread_cond_wait(pthread_cond_t *cond,
                               pthread_mutex_t *mutex);    
         

      Atomically:

      • Block the thread on the conditional variable cond
      • Unlock the mutex variable mutex

      • NOTE: mutex must be locked when pthread_cond_wait() is called, or else, anything (unpredictable results) can happen...

    • pthread_cond_signal(): used to unblock a thread that was block on the condition variable (i.e., take one thread PCB from the queue associated with the conditional variable and make that thread "runnable")

         int pthread_cond_signal(pthread_cond_t *cond);    

    • I will delay the use of Conditional Variable after I discuss binary semaphores



  • (Binary) Semaphores

    • Semaphore:

         sema.phore \'sem-*-.fo-(*)r, -.fo.(*)r\ n [Gk se-ma sign, 
         signal + ISV -phore] 1: an apparatus for visual signaling 
         (as by the position of one or more movable arms) 2: a system 
         of visual signaling by two flags held one in each hand
         

    • The Binary Semaphore was invented by E. W. Dijkstra - a Dutch pioneer in Computer Science while he was directing the T.H.E. (Technical University of Eindhoven) Operating System project

    • Dijkstra developed the semaphore concept when he studied the exclusive resource access problem:

      • Conflicting operations on a common resource (global variable) must happen serially - complete one operation before allowing another operation to even begin....

      • He notice that the real world already has a solution: Trains !

        • A train entering a "critical section" must pass a "semaphore" (signalling) device
        • If the semaphore is up, the train can pass, but as soon as the train crosses the semaphore, it trigger all semaphore signals to go down.
        • The semaphre signals will only be raised (to up) after the train leaves the critical section (triggers the "leave-trigger").

    • The binary semaphore is an object that can take on 2 values: 0 (down) and 1 (up)

    • There are 2 operations on a semaphore:

      • P(s): "Pee semaphore s"

        If s = 1 (up), P(s) returns immediately and the thread that executes the P(s) operation continues - this is the success case (no train in the critical section)

        If s = 0 (down), P(s) blocks - the thread executing the P(s) operation will be made unrunnable (not ready to run) and put in a queue that is associated with the semaphore s.

        Note: the "P" operation is the acronym for Pass - Dutch: Passeren)

      • V(s): "Vee semaphore s"

        Set s = 1 and if the queue that is associated with the semaphore s is not empty, then reactivate a block process from that queue

        Note: V(s) always succeeds (leaving the critical section can always be done).

        Note: the "V" operation is the acronym for leaVe :-) - Just kidding, you need to know Dutch to find the acronym: Verlaten in Dutch means to leave.... hence the V - remember, Dijkstra was a Dutch...)

  • Implementaing binary sempahores with conditional variables

    • Data structure needed to implement a binary semaphore:

        typedef struct BIN_SEMA
        { 
           pthread_cond_t  cv;    /* cond. variable 
      			       - used to block threads */
           pthread_mutex_t mutex; /* mutex variable
      			       - used to prevents concurrent
      			       access to the variable "flag" */
           int     flag;          /* Semaphore state: 
      			       0 = down, 1 = up */
        } bin_sema;
      

    • Initializating a binary semaphore:

        void BinSemaInit(bin_sema **s)
        {
           /* Allocate space for bin_sema */
           *s = (bin_sema *) malloc(sizeof(bin_sema));     
      
           /* Init mutex */
           pthread_mutex_init(&((*s)->mutex), NULL); 
      
           /* Init cond. variable */
           pthread_cond_init(&((*s)->cv), NULL);     
      
           /* Set flag value */
           (*s)->flag = 1;            
        }
      

    • The P-operation:

        void BinSemaP(bin_sema *s)
        { 
           /* Try to get exclusive access to s->flag */
           pthread_mutex_lock(&(s->mutex));     
      
           /* Success - no other thread can get here unless
      	the current thread unlock "mutex"              */
      
           /* Examine the flag and wait until flag == 1 */
           while (s->flag == 0)
           {
              pthread_cond_wait(&(s->cv), &(s->mutex) );
                  /* When the current thread execute this
      	       pthread_cond_wait() statement, the
      	       current thread will be block on s->cv
      	       and (atomically) unlocks s->mutex !!! 
      	       Unlocking s->mutex will let other thread
      	       in to test s->flag.... */
           }
      
           /* -----------------------------------------
              If the program arrive here, we know that 
      	s->flag == 1 and this thread has now
      	successfully pass the semaphore !!!
            ------------------------------------------- */
           s->flag = 0;  /* This will cause all other threads
      		      that executes a P() call to wait 
      		      in the (above) while-loop !!! */
      
           /* Release exclusive access to s->flag */
           pthread_mutex_unlock(&(s->mutex)); 
        }
      

    • The V-operation:

      The V-operation is relatively easy - remember that the thread that calls V() must have been successful in a P() operation and this thread "owns" the semaphore....

        void BinSemaV(bin_sema *s)
        { 
           /* Try to get exclusive access to s->flag */
           pthread_mutex_lock(&(s->mutex));  
      
           pthread_cond_signal(&(s->cv));
      	    /* This call may restart some thread that
      	       was blocked on s->cv (in the P() call)
      		- if there was not thread blocked on
      		- cv, this operation does absolutely
      		- nothing...                          */    
      
           /* Update semaphore state to Up */
           s->flag = 1;
      
           /* Release exclusive access to s->flag */
           pthread_mutex_unlock(&(s->mutex)); 
        }
      

  • Using binary semaphores to access shared variables....

    • Semaphores are more powerful than mutex locks - anywhere you can use a mutex to ensure synchronization, you can also use a semaphore (but not vice versa).

    • Example of using binary semaphores to access shared variables:

        Thread 1:			Thread 2:
      
         BinSemaP(s);			 BinSemaP(s);
         balance = balance + 100; 	 balance = balance - 200; 
         BinSemaV(s);			 BinSemaV(s);
        

    • DEMO: click here

  • Example of the use of binary semaphores:

    The famous "Reader and Writer" synchronization problem can be solved with semaphores, but not with mutexes:

     share variable x
     Reader sums all values written by writer exactly once
    
     Main:
    
        Use 2 semaphores: 
    
    	ReadSema and WriteSema
    
        BinSemaInit(WriteSema, 1);   /* Allow writer to start first */
        BinSemaInit(ReadSema, 0);    /* Block reader until writer 
    				    has written something */
    
     
    Reader Thread: Write thread: BinSemaP(ReadSema); BinSemaP(WriteSema); sum = sum + x; x = new_value(); BinSemaV(WriteSema); BinSemaV(ReadSema);

  • DEMO: click here



  • Other resources:






    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多