mutex example - biuu.cs.biu.ac.il/~myghaz/sadna/mutex example.doc  · web viewsynchronization...

47
NAME pthread_mutex_destroy, pthread_mutex_init - destroy and initialize a mutex SYNOPSIS #include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; The pthread_mutex_init() function initialises the mutex referenced by mutex with attributes specified by attr. If attr is NULL, the default mutex attributes are used; the effect is the same as passing the address of a default mutex attributes object. Upon successful initialisation, the state of the mutex becomes initialised and unlocked. Attempting to initialise an already initialised mutex results in undefined behaviour. In cases where default mutex attributes are appropriate, the macro PTHREAD_MUTEX_INITIALIZER can be used to initialise mutexes that are statically allocated. #include <pthread.h> pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; pthread_mutexattr_t mattr; int ret; /* initialize a mutex to its default value */ ret = pthread_mutex_init(&mp, NULL); NAME pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock - lock and unlock a mutex SYNOPSIS #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_init() Initialize mutex Synopsis: #include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

Upload: ngothuan

Post on 08-Jul-2018

226 views

Category:

Documents


2 download

TRANSCRIPT

NAME pthread_mutex_destroy, pthread_mutex_init - destroy and initialize a mutex

SYNOPSIS #include <pthread.h>

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

The pthread_mutex_init() function initialises the mutex referenced by mutex with attributes specified by attr. If attr is NULL, the default mutex attributes are used; the effect is the same as passing the address of a default mutex attributes object. Upon successful initialisation, the state of the mutex becomes initialised and unlocked.

Attempting to initialise an already initialised mutex results in undefined behaviour.

In cases where default mutex attributes are appropriate, the macro PTHREAD_MUTEX_INITIALIZER can be used to initialise mutexes that are statically allocated.

#include <pthread.h>

pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; pthread_mutexattr_t mattr; int ret;

/* initialize a mutex to its default value */ ret = pthread_mutex_init(&mp, NULL);

NAME pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock - lock and unlock a mutex

SYNOPSIS #include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_init()

Initialize mutex

Synopsis:#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

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

Arguments:mutex

A pointer to the pthread_mutex_t object that you want to initialize. attr

NULL, or a pointer to a pthread_mutexattr_t object that specifies the attributes that you want to use for the mutex. For more information, see pthread_mutexattr_init().

Library:libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:The pthread_mutex_init() function initializes the given mutex object, using the attributes specified by the mutex attributes object attr. If attr is NULL, then the mutex is initialized with the default attributes (see pthread_mutexattr_init()). After initialization, the mutex is in an unlocked state.

You can initialize a statically allocated mutex with the default attributes by assigning to it the macro PTHREAD_MUTEX_INITIALIZER or PTHREAD_RMUTEX_INITIALIZER (for recursive mutexes).

default attributes PTHREAD_MUTEX_INITIALIZER or PTHREAD_RMUTEX_INITIALIZER (for recursive mutexes).

pthread_mutexattr_init()

Initialize a mutex attribute object

Synopsis:#include <pthread.h>

int pthread_mutexattr_init( pthread_mutexattr_t* attr );

Arguments:attr

A pointer to the pthread_mutexattr_t object that you want to initialize.

Library:libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:The pthread_mutexattr_init() function initializes the attributes in the mutex attribute object attr to their default values. After initializing a mutex attribute object, you can use it to initialize one or more mutexes by calling pthread_mutex_init().

The mutex attributes and their default values are:

__protocolPTHREAD_PRIO_INHERIT--when a thread is blocking higher-priority threads by locking one or more mutexes with this attribute, the thread's priority is raised to that of the highest priority thread waiting on the PTHREAD_PRIO_INHERIT mutex.

__recursivePTHREAD_RECURSIVE_DISABLE--threads can't recursively lock a mutex; any thread that tries to lock an already locked mutex becomes blocked.

Mutex Example /* in /cs/cs3013/public/example/mutexthr.c */#include <stdio.h>#include <pthread.h>

/* cc -o mutexthr mutexthr.c -lpthread */

pthread_mutex_t mutex; /* mutex id */main(){ pthread_t idA, idB; /* ids of threads */ void *MyThread(void *);

if (pthread_mutex_init(&mutex, NULL) < 0) { perror("pthread_mutex_init"); exit(1); } if (pthread_create(&idA, NULL, MyThread, (void *)"A") != 0) { perror("pthread_create"); exit(1); } if (pthread_create(&idB, NULL, MyThread, (void *)"B") != 0) { perror("pthread_create"); exit(1); } (void)pthread_join(idA, NULL); (void)pthread_join(idB, NULL); (void)pthread_mutex_destroy(&mutex);}

int x = 0; /* global shared variable */

void *MyThread(void *arg){ char *sbName;

sbName = (char *)arg; IncrementX(); printf("X = %d in Thread %s\n", x, sbName);}

IncrementX(){ int Temp; /* local variable */

BeginRegion(); /* enter critical region */ Temp = x; Temp = Temp + 1; x = Temp; EndRegion(); /* exit critical region */}

BeginRegion(){ pthread_mutex_lock(&mutex);}

EndRegion(){ pthread_mutex_unlock(&mutex);}

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int count = 0;

void* function1( void* arg ){ int tmp = 0;

while( 1 ) { pthread_mutex_lock( &mutex ); tmp = count++; pthread_mutex_unlock( &mutex ); printf( "Count is %d\n", tmp );

/* snooze for 1 second */ sleep( 1 ); }

return 0;}

void* function2( void* arg ){ int tmp = 0;

while( 1 ) { pthread_mutex_lock( &mutex ); tmp = count--; pthread_mutex_unlock( &mutex ); printf( "** Count is %d\n", tmp );

/* snooze for 2 seconds */ sleep( 2 ); }

return 0;}

int main( void ){ pthread_create( NULL, NULL, &function1, NULL ); pthread_create( NULL, NULL, &function2, NULL );

/* Let the threads run for 60 seconds. */ sleep( 60 );

return 0;}

Synchronization Example /* in /cs/cs3013/public/example/pcthreads.c */#include <stdio.h>#include <pthread.h>#include <semaphore.h>

/* cc -o pcthreads pcthreads.c -lpthread -lrt */

sem_t produced, consumed; /* semaphores */int n;main(){ pthread_t idprod, idcons; /* ids of threads */ void *produce(void *); void *consume(void *); int loopcnt = 5;

n = 0; if (sem_init(&consumed, 0, 0) < 0) { perror("sem_init"); exit(1); } if (sem_init(&produced, 0, 1) < 0) { perror("sem_init"); exit(1); } if (pthread_create(&idprod, NULL, produce, (void *)loopcnt) != 0) { perror("pthread_create"); exit(1); } if (pthread_create(&idcons, NULL, consume, (void *)loopcnt) != 0) { perror("pthread_create"); exit(1); } (void)pthread_join(idprod, NULL); (void)pthread_join(idcons, NULL); (void)sem_destroy(&produced); (void)sem_destroy(&consumed);}

void *produce(void *arg){ int i, loopcnt; loopcnt = (int)arg; for (i=0; i<loopcnt; i++) { sem_wait(&consumed); n++; /* increment n by 1 */ sem_post(&produced); }}

void *consume(void *arg){ int i, loopcnt; loopcnt = (int)arg; for (i=0; i<loopcnt; i++) { sem_wait(&produced); printf("n is %d\n", n); /* print value of n */ sem_post(&consumed); }}

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

#define NITER 1000000

int count = 0;

void * ThreadAdd(void * a){ int i, tmp; for(i = 0; i < NITER; i++) { tmp = count; /* copy the global count locally */ tmp = tmp+1; /* increment the local copy */ count = tmp; /* store the local value into the global count */ }}

int main(int argc, char * argv[]){ pthread_t tid1, tid2;

pthread_create(&tid1, NULL, ThreadAdd, NULL); pthread_create(&tid2, NULL, ThreadAdd, NULL); pthread_join(tid1, NULL)) /* wait for the thread 1 to finish */ pthread_join(tid2, NULL); /* wait for the thread 2 to finish */

if (count < 2 * NITER) printf("\n BOOM! count is [%d], should be %d\n", count, 2*NITER); else printf("\n OK! count is [%d]\n", count); pthread_exit(NULL);}

#include <stdio.h>#include <pthread.h>#include <semaphore.h>!!!!!לבדוק על המחשביצרן צרכן של איבר אחד בלבדלסמפורים/* cc -o pcthreads pcthreads.c -lpthread -lrt */

sem_t produced, consumed; /* semaphores */int n;main(){ pthread_t idprod, idcons; /* ids of threads */ void *produce(void *); void *consume(void *); int loopcnt = 5;

n = 0; sem_init(&consumed, 0, 0) < 0) sem_init(&produced, 0, 1) < 0) pthread_create(&idprod, NULL, produce, (void *)loopcnt) pthread_create(&idcons, NULL, consume, (void *)loopcnt)

(void)pthread_join(idprod, NULL); (void)pthread_join(idcons, NULL);

(void)sem_destroy(&produced); (void)sem_destroy(&consumed);}

void *produce(void *arg){ int i, loopcnt; loopcnt = (int)arg; for (i=0; i<loopcnt; i++) { sem_wait(&consumed); n++; /* increment n by 1 */ sem_post(&produced); }}

void *consume(void *arg){ int i, loopcnt; loopcnt = (int)arg; for (i=0; i<loopcnt; i++) { sem_wait(&produced); printf("n is %d\n", n); /* print value of n */ sem_post(&consumed); }}

#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include </usr/include/semaphore.h>לסמפורים#define BUFF_SIZE 5 /* total number of slots */#define NP 3 /* total number of producers */#define NC 3 /* total number of consumers */#define NITERS 4 /* number of items produced/consumed */

typedef struct { int buf[BUFF_SIZE]; /* shared var */ int in; /* buf[in%BUFF_SIZE] is the first empty slot */ int out; /* buf[out%BUFF_SIZE] is the first full slot */ sem_t full; /* keep track of the number of full spots */ sem_t empty; /* keep track of the number of empty spots */ sem_t mutex; /* enforce mutual exclusion to shared data */} sbuf_t;

sbuf_t shared;void *Producer(void *arg){ int i, item, index; index = (int)arg; for (i=0; i < NITERS; i++) { /* Produce item */ item = i; printf("[P%d] Producing %d ...\n", index, item); fflush(stdout);

/* Write item to buf */ /* If there are no empty slots, wait */ sem_wait(&shared.empty); /* If another thread uses the buffer, wait */ sem_wait(&shared.mutex); shared.buf[shared.in] = item; shared.in = (shared.in+1)%BUFF_SIZE; /* Release the buffer */ sem_post(&shared.mutex); /* Increment the number of full slots */ sem_post(&shared.full);

/* Interleave producer and consumer execution */ if (i % 2 == 1) sleep(1); } return NULL;}

void *Consumer(void *arg){ /* Fill in the code here */}

int main(){ pthread_t idP, idC; int index;

sem_init(&shared.full, 0, 0); sem_init(&shared.empty, 0, BUFF_SIZE);

/* Insert code here to initialize mutex*/ for (index = 0; index < NP; index++) { /* Create a new producer */ pthread_create(&idP, NULL, Producer, (void*)index); } /* Insert code here to create NC consumers */ pthread_exit(NULL);}

/* Includes */#include <unistd.h> /* Symbolic Constants */#include <sys/types.h> /* Primitive System Data Types */ #include <errno.h> /* Errors */#include <stdio.h> /* Input/Output */#include <stdlib.h> /* General Utilities */#include <pthread.h> /* POSIX Threads */#include <string.h> /* String handling */#include <semaphore.h> /* Semaphore */

/* prototype for thread routine */void handler ( void *ptr );

/* global vars *//* semaphores are declared global so they can be accessed in main() and in thread routine, here, the semaphore is used as a mutex */sem_t mutex;int counter; /* shared variable */

int main(){ int i[2]; pthread_t thread_a; pthread_t thread_b; i[0] = 0; /* argument to threads */ i[1] = 1; sem_init(&mutex, 0, 1); /* initialize mutex to 1 - binary semaphore */ /* second param = 0 - semaphore is local */ /* Note: you can check if thread has been successfully created by checking return value of pthread_create */ pthread_create (&thread_a, NULL, (void *) &handler, (void *) &i[0]); pthread_create (&thread_b, NULL, (void *) &handler, (void *) &i[1]); pthread_join(thread_a, NULL); pthread_join(thread_b, NULL);

sem_destroy(&mutex); /* destroy semaphore */ /* exit */ exit(0);} /* main() */

void handler ( void *ptr ){ int x; x = *((int *) ptr); printf("Thread %d: Waiting to enter critical region...\n", x); sem_wait(&mutex); /* down semaphore */ /* START CRITICAL REGION */ printf("Thread %d: Now in critical region...\n", x); printf("Thread %d: Counter Value: %d\n", x, counter); printf("Thread %d: Incrementing Counter...\n", x); counter++; printf("Thread %d: New Counter Value: %d\n", x, counter); printf("Thread %d: Exiting critical region...\n", x); /* END CRITICAL REGION */ sem_post(&mutex); /* up semaphore */ pthread_exit(0); /* exit thread */}

allow the programmer to "lock" an object so that only one thread can access it. #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_muteattr_t &mutexattr)int pthread_mutex_lock(pthread_mutex_t *mutex) ;int pthread_mutex_unlock(pthread_mutex_t *mutex) ;int pthread_mutex_destory(pthread_mutex_t *mutex) ;  mutexattr: by default is "fast" ¿Cµ¼ר «עױ א´־¼׃ר־Pג¶Pײ #include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <pthread.h>#include <semaphore.h>void *thread_function(void *arg);pthread_mutex_t work_mutex; /* protects both work_area and time_to_exit */#define WORK_SIZE 1024char work_area[WORK_SIZE];int time_to_exit = 0;int main() {    int res;    pthread_t a_thread;    void *thread_result;    res = pthread_mutex_init(&work_mutex, NULL);    if (res != 0) {        perror("Mutex initialization failed");        exit(EXIT_FAILURE);    }    res = pthread_create(&a_thread, NULL, thread_function, NULL);    if (res != 0) {        perror("Thread creation failed");        exit(EXIT_FAILURE);    }    pthread_mutex_lock(&work_mutex);    printf("Input some text. Enter 'end' to finish\n");    while(!time_to_exit) {        fgets(work_area, WORK_SIZE, stdin);        pthread_mutex_unlock(&work_mutex);        while(1) {            pthread_mutex_lock(&work_mutex);            if (work_area[0] != '\0') {                pthread_mutex_unlock(&work_mutex);                sleep(1);            }            else {                break;            }        }    }    pthread_mutex_unlock(&work_mutex);    printf("\nWaiting for thread to finish...\n");    res = pthread_join(a_thread, &thread_result);    if (res != 0) {        perror("Thread join failed");        exit(EXIT_FAILURE);    }    printf("Thread joined\n");    pthread_mutex_destroy(&work_mutex);    exit(EXIT_SUCCESS);}void *thread_function(void *arg) {    sleep(1);    pthread_mutex_lock(&work_mutex);    while(strncmp("end", work_area, 3) != 0) {        printf("You input %d characters\n", strlen(work_area) -1);        work_area[0] = '\0';        pthread_mutex_unlock(&work_mutex);        sleep(1);        pthread_mutex_lock(&work_mutex);        while (work_area[0] == '\0' ) {            pthread_mutex_unlock(&work_mutex);            sleep(1);            pthread_mutex_lock(&work_mutex);        }    }    time_to_exit = 1;    work_area[0] = '\0';    pthread_mutex_unlock(&work_mutex);    pthread_exit(0);}

Example 1:Two threads displaying two strings “Hello” and “How are you?” independent of each other.

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

void * thread1(){ while(1){ printf("Hello!!\n"); }}

void * thread2(){ while(1){ printf("How are you?\n"); }}

int main(){ int status; pthread_t tid1,tid2;

pthread_create(&tid1,NULL,thread1,NULL); pthread_create(&tid2,NULL,thread2,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); return 0;}

Now compile this program (Note the -l option is to load the pthread library)

$gcc thread.c -lpthread

On running, you can see many interleaved “Hello!!” and “How are you?” messages

Example 2This example involves a reader and a writer thread. The reader thread reads a string from the user and writer thread displays it. This program uses semaphore so as to achieve synchronization

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

char n[1024];sem_t len;

void * read1(){ while(1){ printf("Enter a string"); scanf("%s",n); sem_post(&len); }}

void * write1(){ while(1){ sem_wait(&len); printf("The string entered is :");

printf("==== %s\n",n); }

}

int main(){ int status; pthread_t tr, tw;

pthread_create(&tr,NULL,read1,NULL); pthread_create(&tw,NULL,write1,NULL);

pthread_join(tr,NULL); pthread_join(tw,NULL); return 0;}

On running, in most cases we may be able to achieve a serial read and write( Thread1reads a string and Thread2 displays the same string). But suppose we insert a sleep function() in write1 like

void * write1(){ while(1){ sleep(5); sem_wait(&len); printf("The string entered is :"); printf("==== %s\n",n); }}

The thread 1 may read one more string and thread2 displays the last read string. That is no serial read and write is achieved.

So we may need to use the condition variables to achieve serial read and write.

Example 3This example involves a reader and a writer thread. The reader thread reads a string from the user and writer thread displays it. This program uses condition variables to achieve synchronization and achieve serial programming.

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

#define TRUE 1#define FALSE 0

char n[1024];pthread_mutex_t lock= PTHREAD_MUTEX_INITIALIZER;int string_read=FALSE;

pthread_cond_t cond;

void * read1(){ while(1){ while(string_read); pthread_mutex_lock(&lock); printf("Enter a string: "); scanf("%s",n); string_read=TRUE; pthread_mutex_unlock(&lock); pthread_cond_signal(&cond);

}}

void * write1(){ while(1){ pthread_mutex_lock(&lock); while(!string_read) pthread_cond_wait(&cond,&lock); printf("The string entered is %s\n",n);

string_read=FALSE; pthread_mutex_unlock(&lock); }}int main(){ int status; pthread_t tr, tw;

pthread_create(&tr,NULL,read1,NULL); pthread_create(&tw,NULL,write1,NULL);

pthread_join(tr,NULL); pthread_join(tw,NULL); return 0;}

Use of SIGINT (Cntr+C) is used to quit the program.

#include < stdio.h >#include < pthread.h >#include < semaphore.h >#include < signal.h >

sem_t semname;int qval=1;

void *func1(void *arg) {while(qval) {sem_wait(&semname);printf("Thread one prints # %d\n",(*(int *)(arg))++);sem_post(&semname);sleep(1);}}void *func2(void *arg) {while(qval) {sem_wait(&semname);printf("Thread two prints # %d\n",(*(int *)(arg))++);sem_post(&semname);sleep(1);}}void quit() {qval=0;}

main() {int i=0;signal(SIGINT,quit); //ctrl+csem_init(&semname,0,1);pthread_t thread1,thread2;pthread_create(&thread1,NULL,func1,&i);pthread_create(&thread2,NULL,func2,&i);pthread_join(thread1,&i);pthread_join(thread2,&i);printf("\nQuiting...\n");return 0;}

#include <pthread.h>#include <stdio.h>#include <stdlib.h>#define NITER 1000000000pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;long count = 0, count1 = 0;void * ThreadAdd(void * a) { long i, tmp; for(i = 0; i < NITER; i++)

{count++}}

int main(int argc, char * argv[]) {pthread_t tid1, tid2; pthread_create(&tid1, NULL, ThreadAdd, NULL);pthread_create(&tid2, NULL, ThreadAdd, NULL);pthread_join(tid1, NULL); /* wait for the thread 1 to finish */pthread_join(tid2, NULL); /* wait for the thread 2 to finish */

if (count < 2 * NITER) printf("\n BOOM! count is [%ld], should be %ld\n", count, 2*NITER); else printf("\n OK! count is [%ld]\n", count); pthread_exit(NULL);} BOOM! count is [1971985225], should be 2000000000

/* a simple producer/consumer using semaphores and threads

usage on Solaris: gcc thisfile.c -lpthread -lposix4 a.out numIters

*/ #include <pthread.h>#include <stdio.h>#include <semaphore.h>#define SHARED 1

void *Producer(void *); /* the two threads */void *Consumer(void *);

sem_t empty, full; /* the global semaphores */int data; /* shared buffer */int numIters;

/* main() -- read command line and create threads, then print result when the threads have quit */

int main(int argc, char *argv[]) { /* thread ids and attributes */ pthread_t pid, cid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

numIters = atoi(argv[1]); sem_init(&empty, SHARED, 1); /* sem empty = 1 */ sem_init(&full, SHARED, 0); /* sem full = 0 */

printf("main started\n"); pthread_create(&pid, &attr, Producer, NULL); pthread_create(&cid, &attr, Consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); printf("main done\n");}

/* deposit 1, ..., numIters into the data buffer */void *Producer(void *arg) { int produced; printf("Producer created\n"); for (produced = 0; produced < numIters; produced++) { sem_wait(&empty); data = produced; sem_post(&full); }}

/* fetch numIters items from the buffer and sum them */void *Consumer(void *arg) { int total = 0, consumed; printf("Consumer created\n"); for (consumed = 0; consumed < numIters; consumed++) { sem_wait(&full); total = total+data; sem_post(&empty); } printf("for %d iterations, the total is %d\n", numIters, total);}

#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <semaphore.h>#define NITER 1000000000

sem_t mutex;long count = 0, count1 = 0;void * ThreadAdd(void * a) {

for(long i = 0; i < NITER; i++) {

sem_wait(&mutex);count++;

sem_post(&mutex);}for(i = 0; i < NITER; i++) count++;

}

int main(int argc, char * argv[]) {pthread_t tid1, tid2; sem_init(&mutex,0,1);pthread_create(&tid1, NULL, ThreadAdd, NULL);pthread_create(&tid2, NULL, ThreadAdd, NULL);pthread_join(tid1, NULL); /* wait for the thread 1 to finish */pthread_join(tid2, NULL); /* wait for the thread 2 to finish */

if (count < 2 * NITER) printf("\n BOOM! count is [%ld], should be %ld\n", count, 2*NITER); else printf("\n OK! count is [%ld]\n", count); if (count < 2 * NITER) printf("\n BOOM! count1 is [%ld], should be %ld\n", count1, 2*NITER); else printf("\n OK! count1 is [%ld]\n", count1);

sem_destroy(&mutex); pthread_exit(NULL);} OK! count is [2000000000]BOOM! count is [16372624], should be 2000000000

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

#define BUFF_SIZE 100 /* total number of slots */#define NP 2 /* total number of producers - 1*/#define NC 5 /* total number of consumers - 1 */#define NITERS 4 /* number of items produced/consumed */

typedef struct { int buf[BUFF_SIZE]; /* shared var */ int in; /* buf[in%BUFF_SIZE] is the first empty slot */ int out; /* buf[out%BUFF_SIZE] is the first full slot */ sem_t full; /* keep track of the number of full spots */ sem_t empty; /* keep track of the number of empty spots */ sem_t mutex; /* enforce mutual exclusion to shared data */} sbuf_t;

sbuf_t shared;

/* function prototypes */void *Producer(void *arg);void *Consumer(void *arg);

/* increments and decrements */int produced, consumed;

int main(){ /* producer/consumer threads */ pthread_t idP, idC; /* loop control */ int index; /* temp variables to comp the consumes and produces per hour */ int temp1, temp2; int ratio = temp1 / temp2;

/* keeps track of seconds */ int j = 1;

/* creates semaphores */ sem_init(&shared.full, 0, 0); sem_init(&shared.empty, 0, BUFF_SIZE); sem_init(&shared.mutex, 0, 1);

/* creates producer thread */ for (index = 1; index < NP; index++) { /* Create 1 producer */ pthread_create(&idP, NULL, Producer, (void*)index); }

/* creates 4 consumer threads */ for (index = 1; index < NC; index++) { pthread_create(&idC, NULL, Consumer, (void*)index); }

while(1) { sleep(1); sem_wait(&shared.mutex); temp1 = (consumed * 3600) / j;

temp2 = (produced * 3600) / j; printf("\n\nStatistics after %d seconds: \n\n", j); sem_post(&shared.mutex); printf("So many consumed per hour: %d\n", temp1); printf("So many produced per hour: %d\n", temp2); printf("Ratio of consumed/produced per hour: %d\n", ratio); j++; }

sem_destroy(&shared.mutex); // destroy semaphore pthread_exit(NULL);}void *Producer(void *arg){ int data, index;

index = (int)arg;

printf("Thread %d in line to produce\n\n", index);

for (produced=0; produced < NITERS; produced++) {

/* Produce item */ data = produced; printf("[P%d] Producing %d ...\n\n", index, data); fflush(stdout);

// data=produce_item(shared.buf);

/* If there are no empty slots, wait */ sem_wait(&shared.empty); /* If another thread uses the buffer, wait */ sem_wait(&shared.mutex); /* put new item in buffer */ shared.buf[shared.in] = data; shared.in = (shared.in+1)%BUFF_SIZE; /* leave the critical region */ sem_post(&shared.mutex); /* Increment the number of full slots */ sem_post(&shared.full);

/* Interleave producer and consumer execution */ if (produced % 2 == 1) sleep(1); } return NULL;}

void *Consumer(void *arg){ int data, index;

index = (int)arg;

printf("[C%d] Consuming %d ...\n", index, data); fflush(stdout);

for (consumed=0; consumed < NITERS; consumed++) {

data = consumed;

/* If there are no full slots, wait */ sem_wait(&shared.full); /* If another thread uses the buffer, wait */ sem_wait(&shared.mutex); shared.buf[shared.out] = data; shared.out = (shared.out+1)%BUFF_SIZE; /* Release the buffer */ sem_post(&shared.mutex); /* Increment the number of empty slots */ sem_post(&shared.empty);

/* Interleave producer and consumer execution */ if (consumed % 2 == 1) sleep(1); } return NULL;

}

אתר שלם** Semaphores **

Simple locks let us build a number of concurrent programs correctly andefficiently. Unfortunately, there are some operations that are pretty commonin multithreaded programs that locks alone cannot support (as we will describebelow).

One person who realized this years ago was Edsger Dijkstra, known among otherthings for his famous "shortest paths" algorithm in graph theory [1], an earlypolemic on structured programming entitled "Goto Statements ConsideredHarmful" [2] (what a great title!), and, in the case we will study here, theintroduction of a powerful and flexible synchronization primitive known as the*semaphore* [3].

In this note, we will first describe the semaphore, and then show how it canbe used to solve a number of important synchronization problems.

[SEMAPHORE: DEFINITION]

A semaphore is as an object with an integer value that we can manipulate withtwo routines (which we will call sem_wait() and sem_post() to follow the POSIXstandard). Because the initial value of the semaphore determines its behavior,before calling any other routine to interact with the semaphore, we must firstinitialize it to some value, as this code below does:

--------------------------------------------------------------------------------#include <semaphore.h>sem_t s;sem_init(&s, 0, 1);-------------------------------------------------------------------------------- [FIGURE: INITIALIZING A SEMAPHORE]

In the figure, we declare a semaphore s and initialize it to the value of 1(you can ignore the second argument to sem_init() for now; read the man pagefor details).

After a semaphore is initialized, we can call one of two functions to interactwith it, sem_wait() or sem_post() [4]. The behavior of these two functions isdescribed here:

--------------------------------------------------------------------------------int sem_wait(sem_t *s) { wait until value of semaphore s is greater than 0 decrement the value of semaphore s by 1}

int sem_post(sem_t *s) { increment the value of semaphore s by 1 if there are 1 or more threads waiting, wake 1}-------------------------------------------------------------------------------- [FIGURE: INITIALIZING A SEMAPHORE]

For now, we are not concerned with the implementation of these routines, whichclearly requires some care; with multiple threads calling into sem_wait() andsem_post(), there is the obvious need for managing these critical sectionswith locks and queues similar to how we previously built locks. We will nowfocus on how to *use* these primitives; later we may discuss how they arebuilt.

A couple of notes. First, we can see that sem_wait() will either return rightaway (because the value of the semaphore was 1 or higher when we calledsem_wait()), or it will cause the caller to suspend execution waiting for asubsequent post. Of course, multiple calling threads may call into sem_wait(),and thus all be queued waiting to be woken. Once woken, the waiting threadwill then decrement the value of the semaphore and return to the user.

Second, we can see that sem_post() does not ever suspend the caller. Rather,it simply increments the value of the semaphore and then, if there is a threadwaiting to be woken, wakes 1 of them up.

You should not worry here about the seeming race conditions possible withinthe semaphore; assume that the modifications they make to the state of thesemaphore are all performed atomically.

[BINARY SEMAPHORES, A.K.A. LOCKS]

We are now ready to use a semaphore. Our first use will be one with which weare already familiar: using a semaphore as a lock. Here is a code snippet:

--------------------------------------------------------------------------------sem_t m;sem_init(&m, 0, X); // initialize semaphore to X; what should X be?

sem_wait(&m);// critical section heresem_post(&m);-------------------------------------------------------------------------------- [FIGURE: A SEMAPHORE AS A LOCK]

To build a lock, we simply surround the critical section of interest with asem_wait()/sem_post() pair. Critical to making this work, though, is theinitial value of the semaphore. What should it be?

If we look back at the definition of the sem_wait() and sem_post() routinesabove, we can see that the initial value of the semaphore should be 1. Imaginethe first thread (thread 0) calling sem_wait(); it will first wait for thevalue of the semaphore to be greater than 0, which it is (the semaphore'svalue is 1). It will thus not wait at all and decrement the value to 0 beforereturning to the caller. That thread is now free to enter the criticalsection. If no other thread tries to acquire the lock while thread 0 is insidethe critical section, when it calls sem_post(), it will simply restore thevalue of the semaphore to 1 (and not wake any waiting thread, because thereare no waiting threads).

The more interesting case arises when thread 0 holds the lock (i.e., it hascalled sem_wait() but not yet called sem_post()), and another thread (thread1, say) tries to enter the critical section by calling sem_wait(). In thiscase, thread 1 will find that the value of the semaphore is 0, and thus wait(putting itself to sleep and relinquishing the processor). When thread 0 runsagain, it will eventually call sem_post(), incrementing the value of thesemaphore back to 1, and then wake the waiting thread 0, which will then beable to acquire the lock for itself.

In this basic way, we are able to use semaphores as locks. Because the valueof the semaphore simply alternates between 1 and 0, this usage is sometimesknown as a *binary semaphore*.

[SEMAPHORES AS CONDITION VARIABLES]

Semaphores are also useful when a thread wants to halt its own progresswaiting for something to change. For example, a thread may wish to wait fora list to become non-empty, so that it can take an element off of the list. In this pattern of usage, we often find a thread *waiting* for something tohappen, and a different thread making that something happen and then *signaling* that it has indeed happened, thus waking the waitingthread. Because the waiting thread (or threads, really) is waiting for some*condition* in the program to change, we are using the semaphore as a*condition variable*. We will see condition variables again later,particularly when covering monitors.

A simple example is as follows. Imagine a thread creates another thread andthen wants to wait for it to complete its execution:

--------------------------------------------------------------------------------void *

child(void *arg) { printf("child\n"); // signal here: child is done return NULL;}

intmain(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t c; Pthread_create(c, NULL, child, NULL); // wait here for child printf("parent: end\n"); return 0;}-------------------------------------------------------------------------------- [FIGURE: PARENT WAITING FOR CHILD]

What we would like to see here is the following output:

--------------------------------------------------------------------------------parent: beginchildparent: end-------------------------------------------------------------------------------- [FIGURE: OUTPUT FROM PARENT WAITING FOR CHILD]

The question, then, is how to use a semaphore to achieve this effect, and isit turns out, it is quite simple, as we see here:

--------------------------------------------------------------------------------sem_t s;

void *child(void *arg) { printf("child\n"); // signal here: child is done sem_post(&s); return NULL;}

intmain(int argc, char *argv[]) { sem_init(&s, 0, X); // what should X be? printf("parent: begin\n"); pthread_t c; Pthread_create(c, NULL, child, NULL); // wait here for child sem_wait(&s); printf("parent: end\n"); return 0;}-------------------------------------------------------------------------------- [FIGURE: PARENT WAITING FOR CHILD WITH A SEMAPHORE]

As you can see in the code, the parent simply calls sem_wait() and the childsem_post() to wait for the condition of the child finishing its execution tobecome true. However, this raises the question: what should the initial valueof this semaphore be? (think about it here, instead of reading ahead)

The answer, of course, is that the value of the semaphore should be set to isthe number 0. There are two cases to consider. First, let us assume that theparent creates the child but the child has not run yet (i.e., it is sitting ina ready queue but not running). In this case, the parent will call sem_wait()before the child has called sem_post(), and thus we'd like the parent to waitfor the child to run. The only way this will happen is if the value of thesemaphore is not greater than 0; hence, 0 as the initial value makessense. When the child finally runs, it will call sem_post(), incrementing thevalue to 1 and waking the parent, which will then return from sem_wait() and

complete the program.

The second case occurs when the child runs to completion before the parentgets a chance to call sem_wait(). In this case, the child will first callsem_post(), thus incrementing the value of the semaphore from 0 to 1. When theparent then gets a chance to run, it will call sem_wait() and find the valueof the semaphore to be 1; the parent will thus decrement the value and returnfrom sem_wait() without waiting, also achieving the desired effect.

[THE PRODUCER/CONSUMER (BOUNDED-BUFFER) PROBLEM]

The final problem we will confront in this note is known as the*producer/consumer* problem, or sometimes as the *bounded buffer* problem,which was also first posed by Dijkstra [5]. Indeed, it was theproducer/consumer problem that led to the invention of semaphores! [8]

Imagine one or more producer threads and one or more consumerthreads. Producers produce data items and wish to place them in a buffer;consumers grab data items out of the buffer consume the data in some way.

This arrangement occurs in many places within real systems. For example, in amultithread web server, a producer puts HTTP requests into a work queue (i.e.,the bounded buffer); a thread pool of consumers each take a request out of thework queue and process the request. Similarly, when you use a piped command ina UNIX shell, as follows:

prompt> cat notes.txt | wc -l

This example runs two processes concurrently; "cat file" writes the body ofthe file "notes.txt" to what it thinks is standard output; instead, however,the UNIX shell has redirected the output to what is called a UNIX pipe(created by the *pipe()* system call). The other end of this pipe is connectedto the standard input of the process "wc", which simply counts the number oflines in the input stream and prints out the result. Thus, the "cat" processis the producer, and the "wc" process is the consumer. Between them is abounded buffer.

Because the bounded buffer is a shared resource, we must of course requiresynchronized access to it, lest a race condition arise. To begin to understandthis problem better, let us examine some actual code:

--------------------------------------------------------------------------------int buffer[MAX];int fill = 0; int use = 0;

void put(int value) { buffer[fill] = value; // line F1 fill = (fill + 1) % MAX; // line F2}

int get() { int tmp = buffer[use]; // line G1 use = (use + 1) % MAX; // line G2 return tmp;}-------------------------------------------------------------------------------- [FIGURE: THE PUT AND GET ROUTINES]

In this example, we assume that the shared buffer *buffer* is just an array ofintegers (this could easily be generalized to arbitrary objects, of course),and that the *fill* and *use* integers are used as indices into the array,and are used to track where to both put data (fill) and get data (use).

Let us assume in this simple example that we have just two threads, a producerand a consumer, and that the producer just writes some number of integers intothe buffer which the consumer removes from the buffer and prints:

--------------------------------------------------------------------------------

void *producer(void *arg) { int i; for (i = 0; i < loops; i++) { put(i); }}

void *consumer(void *arg) { int i; for (i = 0; i < loops; i++) { int tmp = get(); printf("%d\n", tmp); }}-------------------------------------------------------------------------------- [FIGURE: THE PRODUCER AND CONSUMER THREADS]

Finally, here is the main body of the program, which simply creates thetwo threads and waits for them to finish.

--------------------------------------------------------------------------------int loops = 0;

int main(int argc, char *argv[]) { loops = atoi(argv[1]);

pthread_t pid, cid; Pthread_create(&pid, NULL, producer, NULL); Pthread_create(&cid, NULL, consumer, NULL); Pthread_join(pid, NULL); Pthread_join(cid, NULL); return 0;}-------------------------------------------------------------------------------- [FIGURE: MAIN BODY OF CODE]

If the program is run with loops = 5, what we'd like to get is the producer"producing" 0, 1, 2, 3, and 4, and the consumer printing them in thatorder. However, without synchronization, we may not get that. For example,imagine if the consumer thread runs first; it will call get() to get data thathasn't even been produced yet, and thus not function as desired. Things getworse when you add multiple producers or consumers, as there could be raceconditions in the update of the use or fill indices. Clearly, something ismissing.

Our first attempt at solving the problem introduces two semaphores, *empty*and *full*, which the threads will use to indicate when a buffer entry hasbeen emptied or filled, respectively. Here is the example code:

--------------------------------------------------------------------------------sem_t empty;sem_t full;

void *producer(void *arg) { int i; for (i = 0; i < loops; i++) { sem_wait(&empty); // line P1 put(i); // line P2 sem_post(&full); // line P3 }}

void *consumer(void *arg) { int i; for (i = 0; i < loops; i++) { sem_wait(&full); // line C1 int tmp = get(); // line C2 sem_post(&empty); // line C3 printf("%d\n", tmp);

}}

int main(int argc, char *argv[]) { // ... sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with... sem_init(&full, 0, 0); // ... and 0 are full // ...}-------------------------------------------------------------------------------- [FIGURE: ADDING THE EMPTY AND FULL CONDITION VARIABLES]

In this example, the producer first waits for a buffer to become empty inorder to put data into it, and the consumer similarly waits for a buffer tobecome filled before using it. Let us first imagine that MAX=1 (there is onlyone buffer in the array), and see if this works.

Imagine again there are two threads, a producer and a consumer. Let us examinea specific scenario on a single CPU. Assume the consumer gets to runfirst. Thus, the consumer will hit line C1 in the figure above, callingsem_wait(&full). Because full was initialized to the value 0, the call willblock the consumer and wait for another thread to call sem_post() on thesemaphore, as desired.

Let's say the producer then runs. It will hit line P1, callingsem_wait(&empty). Unlike the consumer, the producer will continue throughthis line, because empty was initialized to the value MAX (in this case,1). Thus, empty will be decremented to 0 and the producer will put a datavalue into the first entry of buffer (line P2). The producer will thencontinue on to P3 and call sem_post(&full), changing the value of the fullsemaphore from 0 to 1 and waking the consumer (e.g., move it from blocked toready).

In this case, one of two things could happen. If the producer continues torun, it will loop around and hit line P1 again. This time, however, it wouldblock, as the empty semaphore's value is 0. If the producer instead wasinterrupted and the consumer began to run, it would call sem_wait(&full)(line C1) and find that the buffer was indeed full and thus consume it.In either case, we achieve the desired behavior.

You can try this same example with more threads (e.g., multiple producers, andmultiple consumers). It should still work, or it is time to go to sleep.

[A PROBLEM: OFF TO THE RACES]

Let us now imagine that MAX > 1 (say MAX = 10). For this example, let usassume that there are multiple producers and multiple consumers. We now have aproblem: a *race condition*. Do you see where it occurs? (take some time andlook for it)

If you can't see it, here's a hint: look more closely at the put() and get()code.

If you still can't see it, I bet you aren't trying. Come on, spend a minute onit at least.

OK, you win. Imagine two producers both calling into put() at roughly the sametime. Assume producer 1 gets to run first, and just starts to fill the firstbuffer entry (fill = 0 @ line F1). Before the producer gets a chance toincrement the fill counter to 1, it is interrupted. Producer 2 starts to run,and at line F1 it also puts its data into the 0th element of buffer, whichmeans that the old data there is overwritten! This is a no-no; we don't wantany data generated by a producer to be lost.

[SOLUTION? ADDING MUTUAL EXCLUSION]

As you can see, what we've forgotten here is *mutual exclusion*. The fillingof a buffer and incrementing of the index into the buffer is a *criticalsection*, and thus must be guarded carefully. So let's use our friend the

binary semaphore and add some locks. Here is our first try:

--------------------------------------------------------------------------------sem_t empty;sem_t full;sem_t mutex;

void *producer(void *arg) { int i; for (i = 0; i < loops; i++) { sem_wait(&mutex); // line P0 (NEW LINE) sem_wait(&empty); // line P1 put(i); // line P2 sem_post(&full); // line P3 sem_post(&mutex); // line P4 (NEW LINE) }}

void *consumer(void *arg) { int i; for (i = 0; i < loops; i++) { sem_wait(&mutex); // line C0 (NEW LINE) sem_wait(&full); // line C1 int tmp = get(); // line C2 sem_post(&empty); // line C3 sem_post(&mutex); // line C4 (NEW LINE) printf("%d\n", tmp); }}

int main(int argc, char *argv[]) { // ... sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with... sem_init(&full, 0, 0); // ... and 0 are full sem_init(&mutex, 0, 1); // mutex = 1 because it is a lock (NEW LINE) // ...}-------------------------------------------------------------------------------- [FIGURE: ADDING MUTUAL EXCLUSION, BUT INCORRECTLY]

Now we've added some locks around the entire put()/get() parts of the code, asindicated by the NEW LINE comments. That seems like the right idea, but italso doesn't work. Why? Deadlock

[OH OH, DEADLOCK]

Why does deadlock occur? Take a moment to consider it; try to find a casewhere deadlock arises; what sequence of steps must happen for the program todeadlock?

OK, now that you figured it out, here is the answer. Imagine two threads, oneproducer and one consumer. The consumer gets to run first. It acquires themutex (line C0), and then calls sem_wait() on the full semaphore (line C1);because there is no data yet, this call causes the consumer to block and thusyield the CPU; importantly, though, the consumer still *holds* the lock.

A producer then runs. It has data to produce and if it were able to run, itwould be able to wake the consumer thread and all would begood. Unfortunately, the first thing it does is call sem_wait on the binarymutex semaphore (line P0). The lock is already held. Hence, the producer isnow stuck waiting too.

There is a simple cycle here. The consumer *holds* the mutex and is *waiting* for the someone to signal full. The producer could *signal* full but is*waiting* for the mutex. Thus, the producer and consumer are each stuckwaiting for each other: a classic deadlock.

[FINALLY, A SOLUTION THAT WORKS]

To solve this problem, we simply must reduce the scope of the lock. Here isthe final working solution:

--------------------------------------------------------------------------------sem_t empty;sem_t full;sem_t mutex;

void *producer(void *arg) { int i; for (i = 0; i < loops; i++) { sem_wait(&empty); // line P1 sem_wait(&mutex); // line P1.5 (MOVED THE MUTEX TO HERE ...) put(i); // line P2 sem_post(&mutex); // line P2.5 (... AND TO HERE) sem_post(&full); // line P3 }}

void *consumer(void *arg) { int i; for (i = 0; i < loops; i++) { sem_wait(&full); // line C1 sem_wait(&mutex); // line C1.5 (MOVED THE MUTEX TO HERE ...) int tmp = get(); // line C2 sem_post(&mutex); // line C2.5 (... AND TO HERE) sem_post(&empty); // line C3 printf("%d\n", tmp); }}-------------------------------------------------------------------------------- [FIGURE: ADDING MUTUAL EXCLUSION CORRECTLY]

As you can see, we simply move the mutex acquire and release to be just aroundthe critical section; the full and empty wait and signal code is left outside.The result is a simple and working bounded buffer, a commonly-used pattern inmultithreaded programs. Understand it now; use it later. You will thank me foryears to come. Or not.

[A READER-WRITER LOCK]

Another classic problem stems from the desire for a more flexible lockingprimitive that admits that different data structure accesses might requiredifferent kinds of locking. For example, imagine a number of concurrent listoperations, including inserts and simple lookups. While inserts change thestate of the list (and thus a traditional critical section makes sense),inserts simply *read* the data structure; as long as we can guarantee that noinsert is on-going, we can allow many lookups to proceed concurrently. Thespecial type of lock we will now develop to support this type of operation isknown as a *reader-writer lock*. The code for such a lock is available here:

--------------------------------------------------------------------------------typedef struct _rwlock_t { sem_t writelock; sem_t lock; int readers;} rwlock_t;

void rwlock_init(rwlock_t *lock) { readers = 0; sem_init(&lock, 0, 1); // binary semaphore sem_init(&writelock, 0, 1); // used to lock out a writer, or all readers}

void rwlock_acquire_readlock(rwlock_t *lock) { sem_wait(&lock); readers++; if (readers == 1) sem_wait(&writelock);

sem_post(&lock);}

void rwlock_release_readlock(rwlock_t *lock) { sem_wait(&lock); readers--; if (readers == 0) sem_post(&writelock); sem_post(&lock);}

void rwlock_acquire_writelock(rwlock_t *lock) { sem_wait(&writelock);}

void rwlock_release_writelock(rwlock_t *lock) { sem_post(&writelock);}-------------------------------------------------------------------------------- [FIGURE: A SIMPLE READER-WRITER LOCK]

The code is pretty simple. If some thread wants to update the data structurein question, it should call the pair of operations rwlock_acquire_writelock()and rwlock_release_writelock(). Internally, these simply use the "writelock"semaphore to ensure that only a single writer can acquire the lock and thusenter the critical section to update the data structure in question.

More interesting is the rwlock_acquire_readlock() andrwlock_release_readlock() pair. When acquiring a read lock, the reader firstacquires "lock" and then increments the "readers" variable to track how manyreaders are currently inside the data structure. The important step then takenwithin rwlock_acquire_readlock() occurs when the first reader acquires thelock; in that case, the reader also acquires the write lock by callingsem_wait() on the "writelock" semaphore, and then finally releasing the "lock"by calling sem_post().

Thus, once a reader has acquired a read lock, more readers will be allowed toacquire the read lock too; however, any thread that wishes to acquire thewrite lock will have to wait until *all* readers are finished; the last one toexit the critical section will call sem_post() on "writelock" and thus enablea waiting writer to acquire the lock itself.

This approach works (as desired), but does have some negatives, especiallywhen it comes to fairness. In particular, it would be relatively easy forreaders to starve writers. More sophisticated solutions to this problem exist;perhaps you can think of a better implementation? Hint: think about what youwould need to do to prevent more readers from entering the lock once a writeris waiting.

Finally, it should be noted that reader-writer locks should be used with somecaution. They often add more overhead (especially with more sophisticatedimplementations), and thus do not end up speeding up performance as comparedto just using simple and fast locking primitives [7]. Either way, theyshowcase once again how we can use semaphores in an interesting and usefulway.

[THE DINING PHILOSOPHERS]

If I weren't lazy, I would write a bit about one of the most famousconcurrency problems posed and solved by Dijkstra, known as the *diningphilosopher's problem* [9]. However, I am lazy. The problem is famous becauseit is fun and somewhat intellectually interesting; however, its practicalutility is low. I am a practical kind of guy, and thus have a hard timemotivating the time spent to understand something that is so clearlyacademic. Look it up on your own if you are interested.

[SUMMARY]

Semaphores are a powerful and flexible primitive for writing concurrent

programs. Many programmers use them exclusively, shunning simpler locks andcondition variables, due to their great simplicity and utility.

In this note, we have presented just a few classic problems and solutions. Ifyou are interested in finding out more, there are many other materials you canreference. One great (and free reference) is Allen Downey's book onconcurrency [6]. This book has lots of puzzles you can work on to improve yourunderstanding of both semaphores in specific and concurrency in general.Becoming a real concurrency expert takes years of effort; going beyond whatyou learn in class is a key component to mastering such a topic.

[REFERENCES]

[1] "A Note on Two Problems in Connexion with Graphs", E. W. Dijkstra. Numerische Mathematik 1, 269 1959, 271ג€“ . Available: http://www-m3.ma.tum.de/twiki/pub/MN0506/WebHome/dijkstra.pdfCan you believe people worked on algorithms in 1959? I can't.

[2] "Go-to Statement Considered Harmful", E.W. Dijkstra. Communications of the ACM, volume 11(3): pages 147 148ג€“ , March 1968.Available: http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF

[3] "The Structure of the THE Multiprogramming System", E.W. Dijkstra.Communications of the ACM, volume 11(5), pages 341 1968, 346ג€“ . It's pronounced "the T - H - E operating system", not "the THE operatingsystem". Interestingly, this paper that introduced semaphores actually was anearly paper on the art and science of operating system design. Semaphores,which Dijkstra developed to aid in the process of writing the heavilyconcurrent OS, are only found in the appendix of the paper, almost as anafterthought! Indeed, both the use of semaphores as locks as well assemaphores as condition variables are found in the appendix of this paper.

[4] Historically, sem_wait() was first called P() by Dijkstra (for the dutchword "to probe") and sem_post() was called V() (for the dutch word "to test").

[5] "Information Streams Sharing a Finite Buffer", E.W. Dijkstra.Information Processing Letters 1: 179 1972, 180ג€“ .Available: http://www.cs.utexas.edu/users/EWD/ewd03xx/EWD329.PDFDid Dijkstra invent everything? No, but close. He even worked on one of the first systems to correctly provide interrupts!

[6] "The Little Book of Semaphores", A.B. Downey.Available: http://greenteapress.com/semaphores/A great and free book about semaphores.

[7] "Real-world Concurrency", Bryan Cantrill and Jeff Bonwick.ACM Queue. Volume 6, No. 5. September 2008.Available: http://www.acmqueue.org/modules.php?name=Content&pa=showpage&pid=554

[8] "My recollections of operating system design", E.W. Dijkstra.April, 2001. Circulated privately. Available: http://www.cs.utexas.edu/users/EWD/ewd13xx/EWD1303.PDFA fascinating read for those of you interested in how the pioneersof our field came up with some very basic and fundamental concepts,including ideas like "interrupts" and "a stack"!

[9] "Hierarchical ordering of sequential processes", E.W. Dijkstra.Available: http://www.cs.utexas.edu/users/EWD/ewd03xx/EWD310.PDFThe wikipedia page about this problem is also quite informative.

This example involves a reader and a writer thread. The reader thread reads a string from the user and writer thread displays it. This program uses semaphore so as to achieve synchronization

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

char n[1024];sem_t len;

void * read1(){ while(1){ printf("Enter a string"); scanf("%s",n); sem_post(&len); }}

void * write1(){ while(1){ sem_wait(&len); printf("The string entered is :"); printf("==== %s\n",n); }

}

int main(){ int status; pthread_t tr, tw;

pthread_create(&tr,NULL,read1,NULL); pthread_create(&tw,NULL,write1,NULL);

pthread_join(tr,NULL); pthread_join(tw,NULL); return 0;}

On running, in most cases we may be able to achieve a serial read and write( Thread1reads a string and Thread2 displays the same string). But suppose we insert a sleep function() in write1 like

void * write1(){ while(1){ sleep(5); sem_wait(&len); printf("The string entered is :"); printf("==== %s\n",n); }}

The thread 1 may read one more string and thread2 displays the last read string. That is no serial read and write is achieved.

So we may need to use the condition variables to achieve serial read and write.

Example 3This example involves a reader and a writer thread. The reader thread reads a string from the user and writer

thread displays it. This program uses condition variables to achieve synchronization and achieve serial programming.

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

#define TRUE 1#define FALSE 0

char n[1024];pthread_mutex_t lock= PTHREAD_MUTEX_INITIALIZER;int string_read=FALSE;

pthread_cond_t cond;

void * read1(){ while(1){ while(string_read); pthread_mutex_lock(&lock); printf("Enter a string: "); scanf("%s",n); string_read=TRUE; pthread_mutex_unlock(&lock); pthread_cond_signal(&cond); }}

void * write1(){ while(1){ pthread_mutex_lock(&lock); while(!string_read) pthread_cond_wait(&cond,&lock); printf("The string entered is %s\n",n);

string_read=FALSE; pthread_mutex_unlock(&lock); }}int main(){ int status; pthread_t tr, tw;

pthread_create(&tr,NULL,read1,NULL); pthread_create(&tw,NULL,write1,NULL);

pthread_join(tr,NULL); pthread_join(tw,NULL); return 0;}

The output is serial read and write.

In the beginning, I started the discussion that threads are used to achieve concurrency. But the above examples can be easily done by simple scanf and printf i.e.,scanf(“%s”,n);printf(“%s”,n);

But these examples were given to demonstrate the semaphores and condition variables. Example 3 can be further modified to design a reader/writer application. Example string_read boolean variable can be converted to a string_count variable

Comparing APIs for Solaris Threads and POSIX ThreadsThe Solaris threads API and the pthreads API are two solutions to the same problem: build parallelism into application software. Although each API is complete, you can safely mix Solaris threads functions and pthread functions in the same program.

The two APIs do not match exactly, however. Solaris threads support functions that are not found in pthreads, and pthreads include functions that are not supported in the Solaris interface. For those functions that do match, the associated arguments might not, although the information content is effectively the same.

By combining the two APIs, you can use features not found in one API to enhance the other API. Similarly, you can run applications that use Solaris threads exclusively with applications that use pthreads exclusively on the same system.

Major API Differences

Solaris threads and pthreads are very similar in both API action and syntax. The major differences are listed in Table 6–1 .

Table 6–1 Unique Solaris Threads and pthreads Features

Solaris Threads  POSIX Threads 

thr_ prefix for threads function names, sema_ prefix for semaphore function names

pthread_ prefix for pthreads function names, sem_ prefix for semaphore function names

Ability to create “daemon” threads  Cancellation semantics 

Suspending and continuing a thread  Scheduling policies 

Function Comparison Table

The following table compares Solaris threads functions with pthreads functions. Note that even when Solaris threads and pthreads functions appear to be essentially the same, the arguments to the functions can differ.

When a comparable interface is not available either in pthreads or Solaris threads, a hyphen `-' appears in the column. Entries in the pthreads column that are followed by (3RT) are functions in librt, the POSIX.1b Realtime Extensions library, which is not part of pthreads. Functions in this library provide most of the interfaces specified by the POSIX.1b Realtime Extension.

Table 6–2 Solaris Threads and POSIX pthreads Comparison

Solaris Threads  pthreads 

thr_create() pthread_create()

Solaris Threads  pthreads 

thr_exit() pthread_exit()

thr_join() pthread_join()

thr_yield() sched_yield()(3RT)

thr_self() pthread_self()

thr_kill() pthread_kill()

thr_sigsetmask() pthread_sigmask()

thr_setprio() pthread_setschedparam()

thr_getprio() pthread_getschedparam()

thr_setconcurrency() pthread_setconcurrency()

thr_getconcurrency() pthread_getconcurrency()

thr_suspend() - 

thr_continue() - 

thr_keycreate() pthread_key_create()

-  pthread_key_delete()

thr_setspecific() pthread_setspecific()

thr_getspecific() pthread_getspecific()

Solaris Threads  pthreads 

-  pthread_once()

-  pthread_equal()

-  pthread_cancel()

-  pthread_testcancel()

-  pthread_cleanup_push()

-  pthread_cleanup_pop()

-  pthread_setcanceltype()

-  pthread_setcancelstate()

mutex_lock() pthread_mutex_lock()

mutex_unlock() pthread_mutex_unlock()

mutex_trylock() pthread_mutex_trylock()

mutex_init() pthread_mutex_init()

mutex_destroy() pthread_mutex_destroy()

cond_wait() pthread_cond_wait()

cond_timedwait() pthread_cond_timedwait()

cond_reltimedwait() pthread_cond_reltimedwait_np()

Solaris Threads  pthreads 

cond_signal() pthread_cond_signal()

cond_broadcast() pthread_cond_broadcast()

cond_init() pthread_cond_init()

cond_destroy() pthread_cond_destroy()

rwlock_init() pthread_rwlock_init()

rwlock_destroy() pthread_rwlock_destroy()

rw_rdlock() pthread_rwlock_rdlock()

rw_wrlock() pthread_rwlock_wrlock()

rw_unlock() pthread_rwlock_unlock()

rw_tryrdlock() pthread_rwlock_tryrdlock()

rw_trywrlock() pthread_rwlock_trywrlock()

-  pthread_rwlockattr_init()

-  pthread_rwlockattr_destroy()

-  pthread_rwlockattr_getpshared()

-  pthread_rwlockattr_setpshared()

sema_init() sem_init()(3RT)

Solaris Threads  pthreads 

sema_destroy() sem_destroy()(3RT)

sema_wait() sem_wait()(3RT)

sema_post() sem_post()(3RT)

sema_trywait() sem_trywait()(3RT)

fork1() fork()

-  pthread_atfork()

forkall(), multiple thread copy - 

-  pthread_mutexattr_init()

-  pthread_mutexattr_destroy()

type argument in mutex_init() pthread_mutexattr_setpshared()

-  pthread_mutexattr_getpshared()

-  pthread_mutex_attr_settype()

-  pthread_mutex_attr_gettype()

-  pthread_condattr_init()

-  pthread_condattr_destroy()

type argument in cond_init() pthread_condattr_setpshared()

Solaris Threads  pthreads 

-  pthread_condattr_getpshared()

-  pthread_attr_init()

-  pthread_attr_destroy()

THR_BOUND flag in thr_create() pthread_attr_setscope()

-  pthread_attr_getscope()

-  pthread_attr_setguardsize()

-  pthread_attr_getguardsize()

stack_size argument in thr_create() pthread_attr_setstacksize()

-  pthread_attr_getstacksize()

stack_addr argument in thr_create() pthread_attr_setstack()

-  pthread_attr_getstack()

THR_DETACH flag in thr_create() pthread_attr_setdetachstate()

-  pthread_attr_getdetachstate()

-  pthread_attr_setschedparam()

-  pthread_attr_getschedparam()

-  pthread_attr_setinheritsched()

Solaris Threads  pthreads 

-  pthread_attr_getinheritsched()

-  pthread_attr_setsschedpolicy()

-  pthread_attr_getschedpolicy()

To use the Solaris threads functions described in this chapter for Solaris 9 and previous releases, you must link with the Solaris threads library -lthread .

Operation is virtually the same for both Solaris threads and for pthreads, even though the function names or arguments might differ. Only a brief example consisting of the correct include file and the function prototype is presented. Where return values are not given for the Solaris threads functions, see the appropriate pages in man pages section 3: Basic Library Functions for the function return values.

For more information on Solaris related functions, see the related pthreads documentation for the similarly named function.

Where Solaris threads functions offer capabilities that are not available in pthreads, a full description of the functions is provided