Semaphores

EECS 3221.3 • Problems with the software solutions. Fundamentals – Complicated programming, not flexible to use. – Not easy to generalize to more complex synchronization problems. No.6 • (a.k.a. ): an easy-to-use synchronization tool – An integer variable S Synchronization(2) – wait(S) { while (S<=0) ; S-- ; Prof. Hui Jiang } Dept of Electrical Engineering and Computer – (S) { Science, York University S++ ; }

Semaphore usage (1): Semaphore usage (2): the n-process critical-section problem as a General Synchronization Tool • The n processes share a semaphore,

Semaphore mutex ; // mutex is initialized to 1. • Execute B in Pj only after A executed in Pi • Use semaphore flag initialized to 0 Process Pi do { wait(mutex); Pi Pj critical section of Pi! ! … … signal(mutex); A wait (flag) ; signal (flag) ; B remainder section of Pi ! … … ! } while (1);

Spinlock vs. Sleeping Lock Implementation(1)

• In uni-processor machine, disabling before modifying • Previous definition of semaphore requires . semaphore. – It is called spinlock. – spinlock does not need , but waste CPU cycles wait(S) {

in a continuous loop. do { – spinlock is OK only for lock waiting is very short. Disable_Interrupt; signal(S) {

• Semaphore without busy-waiting, called sleeping lock: if(S>0) { S-- ; Disable_Interrupt ; – In defining wait(), rather than busy-waiting, the process makes Enable_Interrupt ; S++ ; system calls to block itself and switch to waiting state, and return ; Enable_Interrupt ; put the process to a waiting queue associated with the } return ; semaphore. The control is transferred to CPU scheduler. Enable_Interrupt ; } – In defining signal(), the process makes system calls to pick a } while(1) ; process in the waiting queue of the semaphore, wake it up by } moving it to the ready queue to wait for CPU . – Sleeping Lock is good only for long waiting.

1 Spinlock Implementation(1) Spinlock Implementation(2) • In multi-processor machine, inhibiting interrupt of all • In uni-processor machine, disabling interrupt before modifying processors is not easy and efficient. semaphore. • Use software solution to critical-section problems – e.g., bakery algorithm. wait(S) { – Treat wait() and signal() as critical sections. do { • Or use hardware support if available: signal(S) { Disable_Interrupt; – TestAndSet() or Swap() if(S>0) { S-- ; Disable_Interrupt ; Enable_Interrupt ; S++ ; • Example: implement spinlock among two processes. return ; Enable_Interrupt ; – Use Peterson’s algorithm for protection. } return ; – Shared data: Enable_Interrupt ; } while(1) ; } } Semaphore S ; Initially S=1

boolean flag[2]; initially flag [0] = flag [1] = false. int turn; initially turn = 0 or 1.

Spinlock Implementation(3) Spinlock Implementation(2) wait(S) { • In multi-processor machine, inhibiting interrupt of all int i=process_ID(); //0P0, 1P1 signal(S) { processors is neither easy nor efficient. int i=process_ID(); //0P0, 1P1 int j=(i+1)%2 ; • Use software solution to critical-section problems int j=(i+1)%2 ; do { – e.g., bakery algorithm.

flag [ i ]:= true; //request to enter – Treat wait() and signal() as critical sections. flag [ i ]:= true; //request to enter turn = j; • Or use hardware support if available: while (flag [ j ] and turn = j) ; turn = j; while (flag [ j ] and turn = j) ; – TestAndSet() or Swap() if (S >0) { //critical section

S--; S++; //critical section • Example: implement spinlock between N processes. flag [ i ] = false; – Use Bakery algorithm for protection. return ; flag [ i ] = false; – Shared data: } else { flag [ i ] = false; return ; Semaphore S ; Initially S=1 } } } while (1); boolean choosing[N]; (Initially false) } int number[N]; (Initially 0 )

Spinlock Implementation(3) Sleeping Lock (I) wait(S) { int i=process_ID(); signal(S) { int i=process_ID(); • Define a sleeping lock as a structure: choosing[ i ] = true; number[ i ] = max(number[0], number[1], choosing[ i ] = true; typedef struct { …, number [N – 1])+1; number[ i ] = max(number[0], number[1], int value; // Initialized to 1 choosing[ i ] = false; …, number [N – 1])+1; for (j = 0; j < N; j++) { choosing[ i ] = false; struct process *L; while (choosing[ j ]) ; for (j = 0; j < N; j++) { } semaphore; while ((number[ j ] != 0) && while (choosing[ j ]) ; (number[ j ],j)< (number[ i ],i)) ; while ((number[ j ] != 0) && } (number[ j ],j)< (number[ i ],i)) ; • Assume two system calls: if (S >0) { //critical section } S--; – block() suspends the process that invokes it. number[i] = 0; S++; //critical section – wakeup(P) resumes the execution of a blocked process P. return ; } number[i] = 0; number[i] = 0; • Equally applicable to multiple threads in one process. } while (1); return ; } }

2 Sleeping Lock (II) Two Types of Semaphores: Binary vs. Counting • Semaphore operations now defined as: • Binary semaphore (a.k.a. mutex lock) – integer value wait(S): can range only between 0 and 1; simpler to implement S.value--; by hardware. if (S.value < 0) { add this process to S.L; • Counting semaphore – integer value can range over an block(); unrestricted domain. }

signal(S): • We can implement a counting semaphore S by using S.value++; two binary semaphore. if (S.value <= 0) { remove a process P from S.L; • Binary semaphore is normally used as mutex lock. wakeup(P); } • Counting semaphore can be used as shared counter, load controller, etc…

Implementing counting semaphore Implementing S with two Binary Semaphores • wait(S) operation: wait_binary(S1); C--; if (C < 0) { • Data structures: signal_binary(S1); binary-semaphore S1, S2; wait_binary(S2); int C: } signal_binary(S1);

• Initialization: • signal(S) operation: S1 = 1 wait_binary(S1); S2 = 0 C ++; C = initial value of semaphore S if (C <= 0) signal_binary(S2); else signal_binary(S1);

Bounded-Buffer P-C Problem Classical Synchronization Problems • A producer produces some data for a consumer to consume. They share a bounded-buffer for data • The Bounded-Buffer P-C Problem transferring. • Shared memory: A buffer to hold at most n items • The Readers-Writers Problem • Shared data (three semaphores)

• The Dining-Philosophers Problem Semaphore filled, empty; /*counting*/ Semaphore mutex; /* binary */

Initially:

filled = 0, empty = n, mutex = 1

3 Bounded-Buffer Problem: Bounded-Buffer Problem: Producer Process Consumer Process

do { do { … wait(filled) produce an item in nextp wait(mutex); … … wait(empty); remove an item from buffer to nextc wait(mutex); … … add nextp to buffer signal(mutex); … signal(empty); signal(mutex); … signal(filled); consume the item in nextc … } while (1); } while (1);

The Readers-Writers Problem The 1st Readers-Writers Problem

• Many processes concurrently access a data object • Use semaphore to implement 1st readers-writer problem – Readers: only read the data. – Writers: update and may write the data object. • Shared data:

• Only writer needs exclusive access of the data. int readcount = 0 ; // keep track the number of readers // accessing the data object • The first readers-writers problem:

– Unless a writer has already obtained permission to use the shared data, readers are always allowed to access data. Semaphore mutex = 1 ; // mutually exclusive access to // readcount among readers – May starve a writer.

• The second readers-writer problem: Semaphore wrt = 1 ; // to the data object // used by every writer – Once a writer is ready, the writer performs its write as soon as possible. //also set by the 1st reader to read the data – May starve a reader. // and clear by the last reader to finish reading

The Dining-Philosophers Problem The 1st Readers-Writers Problem

• Five philosophers are Reader Process Writer Process thinking or eating …! • Using only five …! chopsticks wait(mutex); wait(wrt); readcount++; • When thinking, no need for chopsticks. …! if (readcount == 1) wait(wrt); writing is performed! • When eating, need two signal(mutex); closest chopsticks. …! …! • Can pick up only one signal(wrt); reading is performed! chopsticks … …! • Can not get the one wait(mutex); already in the hand of a readcount--; neighbor. if (readcount == 0) signal(wrt); signal(mutex); …

4 The Dining-Philosophers Problem: Semaphore Solution Incorrect Semaphore Usage

• Represent each chopstick with a semaphore Semaphore chopstick[5]; // Initialized to 1 Mistake 1: Mistake 2: Mistake 3: Mistake 4:

do { Philosopher i … … … wait(chopstick[i]) ; … (i=0,1,2,3,4) signal(mutex) ; wait(mutex) ; wait(mutex) ; wait(chopstick[(i+1) % 5]) ; Critical … … … … Section Critical Critical Critical … eat! Section Section Section … signal(mutex) ; … … … signal(chopstick[i]); wait(mutex) ; wait(mutex) ; signal(chopstick[(i+1) % 5]); … think! … } while (1);

Starvation and double_rq_lock() in • Starvation – infinite blocking. A process may never be double_rq_lock(struct runqueue *rq1, removed from the semaphore queue in which it is struct runqueue *rq2) suspended. { if (rq1 == rq2) • Deadlock – two or more processes are waiting infinitely for an event that can be caused by only one of the waiting spinlock(&rq1->lock); processes. else { • Let S and Q be two semaphores initialized to 1 if (rq1 < rq2) {

P0 P1 spin_lock(&rq1->lock); wait(S); wait(Q); spin_lock(&rq2->lock); wait(Q); wait(S); } else {   spin_lock(&rq2->lock); signal(S); signal(Q); spin_lock(&rq1->lock); signal(Q) signal(S); } } }

Why not? double_rq_unlock() in Linux Kernel double_rq_lock(struct runqueue *rq1, struct runqueue *rq2) { double_rq_unlock(struct runqueue *rq1, spin_lock(&rq1->lock); struct runqueue *rq2) { spin_lock(&rq2->lock); } spin_unlock(&rq1->lock); if (rq1 != rq2) struct runqueue *RdQ, *DevQ1, *DevQ2, … spin_unlock(&rq2->lock); }

P1 P2 … … double_rq_lock(RdQ,DevQ1); double_rq_lock(DevQ1,RdQ); … …

5 Pthread Semaphore Pthread Mutex Lock #include • Pthread semaphores for multi-threaded programming in Unix/Linux: /*declare a mutex variable*/ pthread_mutex_t mutex ; – Pthread Mutex Lock (binary semaphore) /* create a mutex lock */ pthread_mutex_init (&mutex, NULL) ; – Pthread Semaphore (general counting semaphore) /* acquire the mutex lock */ pthread_mutex_lock(&mutex) ;

/* release the mutex lock */ pthread_mutex_unlock(&mutex) ;

Using Pthread Mutex Locks Pthread Semaphores #include • Use mutex locks to solve critical section problems: /*declare a pthread semaphore*/ #include sem_t sem ; pthread_mutex_t mutex ; … /* create and initialize a semaphore */ pthread_mutex_init(&mutex, NULL) ; sem_init (&sem, flag, initial_value) ; … pthread_mutex_lock(&mutex) ; /* wait() operation */

sem_wait(&sem) ; /*** critical section ***/ pthread_mutex_unlock(&mutex) ; /* signal() operation */ sem_post(&sem) ;

Using Pthread semaphore volatile in multithread program

• Using Pthread semaphores for counters shared by multiple threads: · In multithread programming, a shared global variable

must be declared as volatile to avoid compiler’s #include optimization which may cause conflicts: sem_t counter ; … sem_init(&counter, 0, 0) ; /* initially 0 */ volatile int data ; … sem_post(&counter) ; /* increment */ volatile char buffer[100] ; … sem_wait(&counter) ; /* decrement */

6 Process Synchronization nanosleep() for multiple processes in Unix • In Unix, a shared global variable must be created with the following #include systems calls: int nanosleep(const struct timespec *req, #include struct timespec *rem); int shmget(key_t key, size_t size, int shmflg); struct timespec { void *shmat(int shmid, const void *shmaddr, int shmflg); time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds 0-999,999,999 */ }; int shmdt(const void *shmaddr);

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

7