5.3 What are Semaphores?

A semaphore is a synchronization mechanism that can be used to manage synchronization relationships and implement the access policies. A semaphore is a special kind of variable that can only be accessed by very specific operations. The semaphore is used to help threads and processes synchronize access to shared modifiable memory or manage access to a device or other resource. The semaphore is used as a key to access the resource. This key can only be owned by one process or thread at a time. Whichever task owns the key or semaphore locks the resource for its exclusive use. Locking the resource causes any other task that wishes to use the resource to wait until the resource has been unlocked, making it available again. Once unlocked, the next task waiting for the semaphore is given the semaphore, thus accessing the resource. The next task is determined by the scheduling policy in effect for that thread or process.

5.3.1 Semaphore Operations

As mentioned earlier, a semaphore can only be accessed by specific operations like an object. There are two operations that can be performed on a semaphore. The P() operation is a decrement operation and the V() operation is an increment operation. If Mutex is the semaphore, then here are the logical implementations of the P(Mutex) and V(Mutex) operations:

 P(Mutex) if(Mutex > 0) {   Mutex--; } else {     Block on Mutex; } V(Mutex) if(Blocked on Mutex N processes) {   pass on Mutex; } else{     Mutex++; } 

The actual implementation will be system dependent. These operations are indivisible, meaning once the operation is in progress, it cannot be preempted. If several tasks attempt to make a call to the P() operation, only one task will be allowed to proceed. If the Mutex has already been decremented, then the task will block and be placed in a queue. The V() operation is called by the task that has the Mutex . If other tasks are waiting on the Mutex , it is given to the next task in the queue. If no tasks are waiting, then the Mutex is incremented.

Semaphore operations can go by other names :

P() operation:

V() operation:

lock()

unlock()

The value of the semaphore will depend on the type of semaphore it is. There are several types of semaphores. A binary semaphore will have the value 0 or 1. A counting semaphore will have some non-negative integer value.

The POSIX standard defines several types of semaphores. These semaphores are used by processes or threads. Table 5-1 lists the types of semaphores. The table also lists some of their basic operations.

Table 5-1. Semaphore Types Defined by the POSIX Standard and Their Use by Threads and/or Processes

Types of Semaphores

Processes/Threads

Description

Mutex semaphores

Processes or threads

Mechanism used to implement mutual exclusion in a critical section of code.

Read “write locks

Processes or threads

Mechanism used to implement read-write access policy between threads.

Condition variables

Processes or threads

Mechanism used to broadcast a signal between threads that an event has taken place.

   

When a thread locks an event mutex, it blocks until it receives the broadcast.

Multiple condition variables

Processes or threads

Same as an event mutex but includes multiple events or conditions.

Any operating system that is compliant with the Single UNIX Specification or POSIX Standard will supply an implementation of these semaphores. They are a part of the libpthread library and the functions are declared in the pthread.h header.

5.3.2 Mutex Semaphores

The POSIX standard defines a mutex semaphore used by threads and processes of type pthread_mutex_t . This mutex provides the basic operations necessary to make it a practical synchronization mechanism:

  • initialization

  • request ownership

  • release ownership

  • try ownership

  • destruction

Table 5-2 lists the pthread_mutex_t functions that are used to perform these basic operations. The initialization process allocates memory required to hold the mutex semaphore and give the memory some initial values. For a binary semaphore, its initial value will be 0 or 1. If it's a counting semaphore, its initial value is a non-negative number that represents the number of resources available. It can be used to represent the request limit a program is capable of processing in a single session. Unlike regular variables, there is no guarantee that the initialization operation of a mutex will occur. After calling the initialization operation, take precautions to ensure that the mutex was initialized (i.e., checking the return value or checking the errno value). The system shall fail to create the mutex if the space set aside for mutexes has been used, the number of allowable semaphores will be exceeded, the named semaphore already exists, or there is some other memory allocation problem.

Table 5-2. pthread_mutex_t Functions

Mutex Operations

Function Prototypes/Macros #include <pthread.h>

Initialization

 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

Request ownership

 <time.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct tiemspec *restrict abs_timeout); 

Release ownership

 int pthread_mutex_unlock(pthread_mutex_t *mutex); 

Try ownership

 int pthread_mutex_trylock(pthread_mutex_t *mutex); 

Destruction

 int pthread_mutex_destroy(pthread_mutex_t *mutex); 

Similar to a thread, the Pthread mutex has an attribute object that encapsulates all the attributes of the mutex. This mutex attribute will be discussed later. It can be passed to the initialization function, creating a mutex with attributes of those set in the mutex object. If no attribute object is used, the mutex will be intialized with default values. The pthread_mutex_t is initialized as unlocked and private. A private mutex is shared between threads of the same process. A shared mutex is shared between threads of multiple processes. If default attributes are to be used, the mutex can be initialized statically for statically allocated mutex objects by using the macro:

 pthread_mutext Mutex = PTHREAD_MUTEX_INITIALIZER; 

This method uses less overhead but performs no error checking.

A mutex can be owned or unowned. The request ownership operation grants ownership of the mutex to the calling process or thread. Once the mutex is owned, the thread or process has exclusive access to the resource. If there is any attempt to own the mutex (by calling this operation) by any other processes or threads, they are blocked until the mutex is made available. Releasing the mutex causes the next process or thread that has blocked on this mutex to unblock and obtain ownership of the mutex. With pthread_mutex_lock() , the thread granted ownership of a given mutex is the only thread that can release the mutex. A timed version of this function is also available. In that case, if the mutex is owned the process or thread will wait for some specified period of time. If the mutex is not released in that time interval, the process or thread will continue executing.

The try ownership operation tests the mutex to see if it is already owned. If owned, the function returns some value indicating that. The advantage of this operation is the thread or process is not blocked if the mutex is owned. It will be able to continue executing. If the mutex is not owned, then ownership is granted.

The destruction operation frees the memory associated with the mutex. The memory cannot be destroyed or closed if it is owned or a thread or process is waiting for the mutex.

5.3.2.1 Using the Mutex Attribute Object

The pthread_mutex_t has an attribute object used in a similar way as the thread attribute. The attribute object encapsulates all the attributes of a mutex object. Once initialized, it can be used by multiple mutex objects when passed to the pthread_mutex_init() function. The mutex attribute defines several functions used to set these attributes: priority ceiling, protocol, and type. These functions and other attribute functions are listed in Table 5-3 with a brief description.

Table 5-3. pthread_mutex_t Attribute Object Functions

pthread_mutex_t Attribute Object Function Prototypes #include <pthread.h>

Description

 int pthread_mutexattr_init (pthread_mutexattr_t * attr); 

Initializes a mutex attribute object specified by the parameter attr with default values for all of the attributes defined by the implementation.

 int pthread_mutexattr_destroy (pthread_mutexattr_t * attr); 

Destroys a mutex attribute object specified by the parameter attr , which causes the mutex attribute object to become uninitialized . Can be reinitialized by calling the pthread_mutexattr_init() function.

 int pthread_mutexattr_ setprioceiling (pthread_mutexattr_t * attr,  int prioceiling); int pthread_mutexattr_ getprioceiling (const pthread_mutexattr_t *  restrict attr, int *restrict prioceiling); 

Sets and returns the priority ceiling attribute of the mutex specified by the parameter attr . The parameter prioceiling contains the priority ceiling of the mutex. The prioceiling attribute defines the minimum priority level at which the critical section guarded by the mutex is executed. The values are within the maximum range of priorities defined by SCHED_FIFO .

 int pthread_mutexattr_ setprotocol (pthread_mutexattr_t * attr,  int protocol); 

Sets and returns the protocol of the mutex attribute specified by the parameter attr . The protocol parameter contains the value of the protocol attribute:

 int pthread_mutexattr_ getprotocol (const pthread_mutexattr_t *  restrict attr,  int *restrict protocol); 

PTHREAD_PRIO_NONE

The priority and scheduling of the thread is not affected by the ownership of the mutex.

 

PTHREAD_PRIO_INHERIT

Thread blocking other threads of higher priority due to ownership of such a mutex, shall execute at the highest priority of any of the threads waiting on any of the mutexes owned by this thread with such a protocol.

 

PTHREAD_PRIO_PROTECT

Threads owning such a mutex shall execute at the highest priority ceilings of all mutexes owned by this thread with such a protocol, regardless of whether other threads are blocked on any of these mutexes.

 int pthread_mutexattr_ setpshared (pthread_mutexattr_t * attr,  int pshared); 

Sets or returns the process-shared attribute of the mutex attribute object specified by the parameter attr . The pshared parameter contains a value:

 int pthread_mutexattr_ getpshared (const pthread_mutexattr_t *  restrict attr, int *restrict pshared); 

PTHREAD_PROCESS_SHARED

Permits a mutex to be shared by any threads that have access to the allocated memory of the mutex even if the threads are in different processes.

 

PTHREAD_PROCESS_PRIVATE

Mutex is shared between threads of the same process as the initialized mutex.

 int pthread_mutexattr_ settype (pthread_mutexattr_t * attr,  int type); 

Sets and returns the type mutex attribute of the mutex attribute specified by the parameter attr . The mutex type attribute is used to describe the behavior of the mutex, which includes whether the mutex will determine deadlock, perform error checking, etc. The type parameter contains a value:

 int pthread_mutexattr_ gettype (const pthread_mutexattr_t *  restrict attr,  int *restrict type); 

PTHREAD_MUTEX_DEFAULT

PTHREAD_MUTEX_RECURSIVE

PTHREAD_MUTEX_ERRORCHECK

PTHREAD_MUTEX_NORMAL

The most interesting of the attributes is setting whether the mutex will be private or shared. Private mutexes are only shared among threads of the same process. It can be declared as global or a handle can be passed between threads. Shared mutexes are used by any threads that have access to the memory in which the mutex is located. This includes threads of different processes. Figure 5-5 contrasts the idea of private and shared mutexes between different processes. If threads of different processes are to share a mutex, it must be allocated in memory shared between processes. POSIX defines several functions used to allocate shared memory between objects using memory-mapped files and shared memory objects. Mutexes between processes can be used to protect critical sections that access files, pipes, shared memory, and devices.

Figure 5-5. Private and shared mutexes.

graphics/05fig05.gif

5.3.2.2 Using Mutex Semaphores to Manage Critical Sections

Mutexes can be used to manage critical sections of processes and threads in order to control race conditions. Mutexes avoid race conditions by serializing access to the critical section. Example 5.1 shows two threads. Mutexes are used to protect their critical sections.

Example 5.1 Using mutexes to protect critical sections of threads.
 // ... pthread_t ThreadA,ThreadB; pthread_mutex_t Mutex; pthread_mutexattr_t MutexAttr; void *task1(void *X) {    pthread_mutex_lock(&Mutex);  // critical section of code  pthread_mutex_unlock(&Mutex);    return(0); } void *task2(void *X) {    pthread_mutex_lock(&Mutex);  // critical section of code  pthread_mutex_unlock(&Mutex);    return(0); } int main(void) {    //...    pthread_mutexattr_init(&MutexAttr);    pthread_mutex_init(&Mutex,&MutexAttr);    //set mutex attributes    pthread_create(&ThreadA,NULL,task1,NULL);    pthread_create(&ThreadB,NULL,task2,NULL);    //...    return(0); } 

In Example 5.1, ThreadA and ThreadB have critical sections protected by their use of Mutex .

Example 5.2 shows how mutexes can be used to protect the critical sections of currently executing processes.

Example 5.2 Mutexes used to protect critical sections.
 //... int Rt; pthread_mutex_t Mutex1; pthread_mutexattr_t MutexAttr; int main(void) {    //...    pthread_mutexattr_init(&MutexAttr);  pthread_mutexattr_setpshared(&MutexAttr,   PTHREAD_PROCESS_SHARED);  pthread_mutex_init(&Mutex1,&MutexAttr);    if((Rt = fork()) == 0){ // child process       pthread_mutex_lock(&Mutex1);  //critical section  pthread_mutex_unlock(&Mutex1);    }    else{ // parent process       pthread_mutex_lock(&Mutex1);  //critical section  pthread_mutex_unlock(&Mutex1);    }    //...    return(0); } 

In Example 5.2, it is important to note that the mutex has been initialized as shared by calling:

 pthread_mutexattr_setpshared(&MutexAttr,PTHREAD_PROCESS_SHARED); 

This allows Mutex to be shared by threads of different processes. Once fork() is called, the child process and parent process can protect their critical section with Mutex . Their critical sections will contain some resource shared by both processes.

5.3.3 Read “Write Locks

Mutex semaphores are used to manage a critical section by serializing entry to that section. Only one thread or process is permitted to enter the critical section at a time. With read-write locks , multiple threads are allowed to enter the critical section if they are to read the shared memory only. Therefore, any number of threads can own a read-write lock for reading. But if multiple threads are to write or modify the shared memory, only one thread is given access. No other threads are allowed to enter the critical section if one thread is given exclusive access to write to the shared memory. This can be used when applications more often read data than write data. If the application has multiple threads, mutex exclusion can be extreme. The performance of the application can benefit by allowing multiple reads. The POSIX standard defines a read-write lock of type pthread_rwlock_t .

Similar to mutex semaphores, the read-write locks have the same operations. Table 5-4 lists the read-write lock operations.

The difference between regular mutexes and read-write mutexes is their locking request operations. Instead of one locking operation there are two:

 pthread_rwlock_rdlock() pthread_rwlock_wrlock() 

pthread_rwlock_rdlock() obtains a read-lock and pthread_rwlock_wrlock() obtains a write lock. If a thread requests a read lock, it is granted the lock as long as there are no threads that hold a write lock. If so, the calling thread is blocked. If a thread request a write lock, it is granted as long as there are no threads that hold a read lock or a write lock. If so, the calling thread is blocked.

The read-write lock is of type pthread_rwlock_t . This type also has an attribute object that encapsulates its attributes. The attribute functions are listed in Table 5-5.

Table 5-4. Read “Write Lock Operations

Read “write Lock Operations

Function Prototypes #include <pthread.h>

Initialization

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

Request ownership

 #include <time.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout); int pthread_rwlock_timedwrlock(pthread_rwlock_t  *restrict rwlock, const struct timespec *restrict abs_timeout); 

Release ownership

 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 

Try ownership

 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 

Destruction

 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 

The pthread_rwlock_t can be private between threads or shared between threads or different processes.

5.3.3.1 Using Read-Write Locks to Implement Access Policy

Read-write locks can be used to implement an access policy, namely CREW. Several tasks can be granted concurrent reads but only one task is granted write access. Using read-write locks will not permit concurrent reads to occur with the exclusive write. Example 5.3 contains tasks using read-write locks to protect critical sections.

Table 5-5. Attribute Object Functions for pthread_rwlock_t

pthread_rwlock_t Attribute Object Function Prototypes #include <pthread.h>

Description

 int pthread_rwlockattr_init (pthread_rwlockattr_t * attr); 

Initializes a read-write lock attribute object specified by the parameter attr with default values for all of the attributes defined by the implementation.

 int pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr); 

Destroys a read-write lock attribute object specified by the parameter attr . Can be reinitialized by calling the pthread_rwlockattr_init() function.

 int pthread_rwlockattr_ setpshared (pthread_rwlockattr_t * attr,  int pshared); 

Sets or returns the process-shared attribute of the read-write lock attribute object specified by the parameter attr . The pshared parameter contains a value:

 int pthread_rwlockattr_ getpshared (const pthread_rwlockattr_t *  restrict attr,  int *restrict pshared); 

PTHREAD_PROCESS_SHARED

Permits a read-write lock to be shared by any threads that have access to the allocated memory of the read-write lock even if the threads are in different processes.

 

PTHREAD_PROCESS_PRIVATE

The read-write lock is shared between threads of the same process as the initialized rwlock.

Example 5.3 Threads using read-write locks.
 //... pthread_t ThreadA,ThreadB,ThreadC,ThreadD; pthread_rwlock_t RWLock; void *producer1(void *X) {    pthread_rwlock_wrlock(&RWLock);  //critical section  pthread_rwlock_unlock(&RWLock);    return(0); } void *producer2(void *X) {    pthread_rwlock_wrlock(&RWLock);  //critical section  pthread_rwlock_unlock(&RWLock); } void *consumer1(void *X) {    pthread_rwlock_rdlock(&RWLock);  //critical section  pthread_rwlock_unlock(&RWLock);    return(0); } void *consumer2(void *X) {    pthread_rwlock_rdlock(&RWLock);  //critical section  pthread_rwlock_unlock(&RWLock);    return(0); } int main(void) {    pthread_rwlock_init(&RWLock,NULL);    //set mutex attributes    pthread_create(&ThreadA,NULL,producer1,NULL);    pthread_create(&ThreadB,NULL,consumer1,NULL);    pthread_create(&ThreadC,NULL,producer2,NULL);    pthread_create(&ThreadD,NULL,consumer2,NULL);    //...    return(0); } 

In Example 5.3, four threads are created. Two threads are producers , ThreadA and ThreadC , and two threads are consumers, ThreadB and ThreadD . All the threads have a critical section and each section is protected with the read “write lock RWLock . As mentioned, ThreadB and ThreadD can enter their critical sections concurrently or serially but neither thread can enter their critical sections if either ThreadA or ThreadC is in theirs. ThreadA and ThreadC cannot enter their critical sections concurrently. Table 5-6 shows part of the decision table for Example 5.3.

Table 5-6. Part of the Decision Table for Example 5.3

Thread A (writer)

Thread B (reader)

Thread C (writer)

Thread D (reader)

N

N

N

Y

N

N

Y

N

N

Y

N

N

N

Y

N

Y

Y

N

N

N

5.3.4 Condition Variables

A condition variable is a semaphore used to signal an event has occurred. One or more processes or threads can wait for the signal sent by other processes or threads once the event has taken place. Some make a distinction between condition variables and the mutex semaphores discussed. The purpose of the mutex semaphore and read “write locks is to synchronize data access whereas condition variables are typically used to synchronize the sequence of operations. W. Richard Stevens, in his book UNIX Network Programming , states it best: "Mutexes are for locking and cannot be used for waiting ."

In Program 4.6, our consumer thread contained a busy loop:

 15 while(TextFiles.empty()) 16 {} 

The consumer thread looped until there were items in the TextFiles queue. This can be replaced by a condition variable. The producer can signal the consumer that items have been inserted into the queue. The consumer can wait until it receives the signal then continue to process the queue.

The condition variable is of type pthread_cond_t . These are the types of operations it can perform:

  • initialize

  • destroy

  • wait

  • timed wait

  • signal

  • broadcast

The initialize and destroy operations work in a similar manner as the other mutexes. Table 5-7 lists the functions for the pthread_cond_t that implement these operations.

Table 5-7. Functions for the pthread_cond_t that Implement Condition Variables Operations

Condition Variables Operations

Function Prototypes/Macros #include <pthread.h>

Initialization

 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 

Waiting

 int pthread_cond_wait(pthread_cond_t * restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t * restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 

Signaling

 int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); 

Destruction

 int pthread_cond_destroy(pthread_cond_t *cond); 

Condition variables are used in conjunction with mutexes. If a thread or process attempts to lock a mutex, we know that it will block until the mutex is released. Once unblocked, it obtains the mutex then continues. If a condition variable is used, it must be associated with a mutex.

 //... pthread_mutex_lock(&Mutex); pthread_cond_wait(&EventMutex,&Mutex); //... pthread_mutex_unlock(&Mutex); 

A task attempts to lock a mutex. If the mutex is already locked then the task will block. Once unblocked, the task will release the mutex, Mutex , while it waits on the signal for the condition variable, EventMutex . If the mutex is not locked, it will release the mutex and wait indefinitely. With a timed wait, the task will only wait for a specified period of time. If the time expires before the task is signaled, the function will return an error. It will then reacquire the mutex.

The signal operation causes a task to signal to another thread or process that an event has occurred. If a task is waiting for that condition variable, it will be unblocked and given the mutex. If there are several tasks waiting for the condition variable, only one will be unblocked. The tasks will be waiting in a queue and unblocked according to the scheduling policy. The broadcast operation signals all the task waiting for the condition variable. If multiple tasks are unblocked, the tasks shall compete for the ownership of the mutex according to a scheduling policy. In contrast to the wait operation, the signaling task is not required to own the mutex, although it is recommended.

The condition variable also has an attribute object. Table 5-8 lists the functions of the attribute object with a brief description.

5.3.4.1 Using Condition Variables to Manage Synchronization Relationships

The condition variable can be used to implement the synchronization relationships mentioned earlier: start-to-start (SS), finish-to-start (FS), start-to-finish (SF), and finish-to-finish (FF). These relationships can exist between threads of the same processes or different processes. Examples 5.4 and 5.5 contain examples of how to implement an FS and FF synchronization relationship. There are two mutexes used in each example. One mutex is used to synchronize access to the shared data and the other mutex is used to synchronize execution of code.

Example 5.4 FS synchronization relationship between two threads.
 //... float Number; pthread_t ThreadA,ThreadB; pthread_mutex_t Mutex,EventMutex; pthread_cond_t Event; void *worker1(void *X) {   for(int Count = 1;Count < 100;Count++){      pthread_mutex_lock(&Mutex);      Number++;      pthread_mutex_unlock(&Mutex);      cout << "worker1: number is " << Number << endl;      if(Number == 50){         pthread_cond_signal(&Event);      }   }   cout << "worker 1 done" << endl;   return(0); } void *worker2(void *X) {    pthread_mutex_lock(&EventMutex);    pthread_cond_wait(&Event,&EventMutex);    pthread_mutex_unlock(&EventMutex);    for(int Count = 1;Count < 50;Count++){       pthread_mutex_lock(&Mutex);       Number = Number + 20;       pthread_mutex_unlock(&Mutex);       cout << "worker2: number is " << Number << endl;    }    cout << "worker 2 done" << endl;    return(0); } int main(int argc, char *argv[]) {    pthread_mutex_init(&Mutex,NULL);    pthread_mutex_init(&EventMutex,NULL);    pthread_cond_init(&Event,NULL);    pthread_create(&ThreadA,NULL,worker1,NULL);    pthread_create(&ThreadB,NULL,worker2,NULL);    //...    return(0); } 
Table 5-8. Functions of the Attribute Object for the Condition Variable of Type pthread_cond_t

pthread_cond_t Attribute Object Function Prototypes #include <pthread.h>

Description

 int pthread_condattr_init (pthread_condattr_t * attr); 

Initializes a condition variable attribute object specified by the parameter attr with default values for all of the attributes defined by the implementation.

 int pthread_condattr_destroy (pthread_condattr_t * attr); 

Destroys a condition variable attribute object specified by the parameter attr . Can be reinitialized by calling the pthread_condattr_init() function.

 int pthread_condattr_ setpshared (pthread_condattr_t * attr,  int pshared); 

Sets or returns the process-shared attribute of the condition variable attribute object specified by the parameter attr . The pshared parameter contains a value:

 int pthread_condattr_ getpshared (const pthread_condattr_t  * restrict attr,  int *restrict pshared); 

PTHREAD_PROCESS_SHARED

Permits a read “write lock to be shared by any threads that have access to the allocated memory of the condition variable even if the threads are in different processes.

 

PTHREAD_PROCESS_PRIVATE

The condition variable is shared between threads of the same process as the initialized condition.

 int pthread_condattr_ setclock (pthread_condattr_t  * attr, clockid_t clock_id); int pthread_condattr_ getclock (const pthread_condattr_t  * restrict attr,  clockid_t *  restrict clock_id); 

Sets and returns the clock attribute for the condition variable attribute object specified by the parameter attr . The clock attribute is the clock id of the clock used to measure the timeout service of the pthread_cond_timedwait() function. The default value of the clock attribute is the system clock.

In Example 5.4, the FS synchronization relationship is implemented. ThreadA cannot finish until ThreadB starts. ThreadA signals to ThreadB once Number has a value of 50 . It can now continue execution until finished. ThreadB cannot start its computation until it gets a signal from ThreadA . ThreadB uses the EventMutex with the condition variable Event . Mutex is used to synchronize write access to the shared data Number . A task can use several mutexes to synchronize different critical sections and synchronize different events.

Example 5.5 contains an implementation of a FF synchronization relationship.

Example 5.5 FF synchronization relationship between two threads.
 //... float Number; pthread_t ThreadA,ThreadB; pthread_mutex_t Mutex,EventMutex; pthread_cond_t Event; void *worker1(void *X) {    for(int Count = 1;Count < 10;Count++){       pthread_mutex_lock(&Mutex);       Number++;       pthread_mutex_unlock(&Mutex);       cout << "worker1: number is " << Number << endl;    }    pthread_mutex_lock(&EventMutex);    cout << "worker1 done now waiting " << endl;    pthread_cond_wait(&Event,&EventMutex);    pthread_mutex_unlock(&EventMutex);    return(0); } void *worker2(void *X) {    for(int Count = 1;Count < 100;Count++){       pthread_mutex_lock(&Mutex);       Number = Number * 2;       pthread_mutex_unlock(&Mutex);       cout << "worker2: number is " << Number << endl;    }    pthread_cond_signal(&Event);    cout << "worker2 done now signalling " << endl;    return(0); } int main(int argc, char *argv[]) {    pthread_mutex_init(&Mutex,NULL);    pthread_mutex_init(&EventMutex,NULL);    pthread_cond_init(&Event,NULL);    pthread_create(&ThreadA,NULL,worker1,NULL);    pthread_create(&ThreadB,NULL,worker2,NULL);    //...    return(0); } 

In Example 5.5, ThreadA cannot finish until ThreadB finishes. ThreadA must iterate through the loop 10 times, where ThreadB must iterate through the loop only 100 times. ThreadA will complete its iterations before ThreadB but will wait until ThreadB signals that it is done.

SS and SF can be implemented in a similar manner. These techniques can easily be used to synchronize order of execution between processes.



Parallel and Distributed Programming Using C++
Parallel and Distributed Programming Using C++
ISBN: 0131013769
EAN: 2147483647
Year: 2002
Pages: 133

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net