Announcements Cooperating Processes Operating systems allow for the creation and concurrent execution of multiple processes & threads eases program complexity increases efficiency Can they work together? How? Messages? What about shared memory? Problems with concurrent execution Concurrent processes (or threads) often need to share data (maintained either in shared memory or files) and resources If there is no controlled access to shared data, some processes will obtain an inconsistent view of this data The action performed by concurrent processes will then depend on the order in which their execution is interleaved Consider two threads one doing x++ and the other doing x--. How many different values for x? The Critical-Section Problem Consider a system: n processes {P0, P1, …, Pn-1} Each process has a critical section changing common values updating tables etc. Access to those variables must be safe mutually exclusive The Critical-Section Problem A solution must satisfy 3 conditions: Mutual exclusion Progress Bounded waiting No assumptions can be made about speed Solutions execute some entry code and some exit code surrounding critical section. Critical Section Properties Mutual Exclusion At any time, at most one process can be in its critical section (CS) Progress Only processes that are not executing in their CS can participate in the decision of who will enter next in the CS. This selection cannot be postponed indefinitely Critical Section Properties Bounded Waiting After a process has made a request to enter it’s CS, there is a bound on the number of times that the other processes are allowed to enter their CS otherwise the process will suffer from starvation Of course there must also be no deadlock pthread_mutex int pthread_mutex_init( pthread_mutex_t *mutex_lock, const pthread_mutexattr_t *lock_attr); int pthread_mutex_lock( pthread_mutex_t *mutex_lock); int pthread_mutex_unlock( pthread_mutex_t *mutex_lock); int pthread_mutex_trylock( pthread_mutex_t *mutex_lock); #include <pthread.h> void *find_min(void *list_ptr) pthread_mutex_t minimum_value_lock; int minimum_value, partial_list_size; main(){ minimum_value = MIN_INT; pthread_init(); pthread_mutex_init(&minimum_value_lock, NULL); /*inititalize lists etc, create and join threads*/ } void *find_min(void *list_ptr){ int *partial_list_ptr, my_min = MIN_INT, i; partial_list_ptr = (int *)list_ptr; for (i = 0; i < partial_list_size; i++) if (partial_list_ptr[i] < my_min) my_min = partial_list_ptr[i]; pthread_mutex_lock(minimum_value_lock); if (my_min < minimum_value) minimum_value = my_min; pthread_mutex_unlock(minimum_value_lock); pthread_exit(0); } Locking Overhead Serialization points Minimize the size of critical sections Be careful Rather than wait, check if lock is available pthread_mutex_trylock If already locked, will return EBUSY Will require restructuring of code pthread_mutex_trylock /* Finding k matches in a list */ void *find_entries(void *start_pointer) { /* This is the thread function */ struct database_record *next_record; int count; current_pointer = start_pointer; do { next_record = find_next_entry(current_pointer); count = output_record(next_record); } while (count < requested_number_of_records); } int output_record(struct database_record *record_ptr) { int count; pthread_mutex_lock(&output_count_lock); output_count ++; count = output_count; pthread_mutex_unlock(&output_count_lock); if (count <= requested_number_of_records) print_record(record_ptr); return (count); } pthread_mutex_trylock /* rewritten output_record function */ int output_record(struct database_record *record_ptr) { int count; int lock_status; lock_status=pthread_mutex_trylock(&output_count_lock); if (lock_status == EBUSY) { insert_into_local_list(record_ptr); return(0); } else { count = output_count; output_count += number_on_local_list + 1; pthread_mutex_unlock(&output_count_lock); print_records(record_ptr, local_list, requested_number_of_records - count); } } return(count + number_on_local_list + 1); Cooperation through shared mem. x1 x P0 1. Wait until x has a value x2 x3 … xn P1 1. Wait until x is able to be set 2. Use the value 2. Produce a value 3. Change the value to indicate used 3. Set x to the value Can we increase the parallelism? What problems does this entail? The Producer-Consumer Problem repeat … produce an item in nextp … while counter == n do no-op buffer[in] = nextp in = (in + 1) mod n counter = counter + 1 until false repeat … while counter == 0 do no-op nextc = buffer[out] out = (out + 1) mod n counter = counter -1 … consume the item in nextc … until false Are these correct? Always? Semaphores Synchronization tool provided by the OS Integer variable that gives us two operations wait(s) while s <= 0 do nothing s=s-1 signal(s) s=s+1 Modifications to s are atomic. Semaphores for Critical Sections Shared semaphore: mysem= 1; repeat wait(mysem) critical section signal(mysem) remainder section until false Will this work for n processes? Semaphore Implementation How do we wait? spin? sleep? – How long? How do we wake up? Solution: Let process block itself by placing in waiting queue wait call places the process on the queue When a process is blocked, it must be woken up signal process must wake up next process on queue Semaphore struct semaphore { int Queue }; value; processes; Wait wait(Semaphore s) { s.value = s.value - 1; if (s.value < 0) { add this process to s.L block; } } Signal signal(Semaphore s) { s.value = s.value + 1; if (s.value <= 0) { remove a process P from s.L wakeup(P); } } Details Critical Semaphore operations must be atomic Uniprocessor simply inhibit interrupts (normal user can’t) Use TestAndSet instruction Multiprocessor hardware must provide special support or use software solutions Using semaphores Two processes P1 and P2 Statements S1 and S2 S2 must execute only after S1 P1: P2: S1; signal(synch); wait(synch); S2; Consider P0: P1: wait(S); wait(Q); . . . signal(S); signal(Q); wait(Q); wait(S); . . . signal(Q); signal(S); Is there anything wrong with this? Semaphores Semaphores can be: binary counting Binary integer variable is 0 or 1 strictly a mutual exclusion variable pthread_mutex Counting integer variable indicates quantity allows more than one process/thread in at a time Bounded Buffer Problem x1 x2 x3 … xn P0 P1 Bounded Buffer Solution Shared semaphore: empty = n, full = 0, mutex = 1; repeat repeat produce an item in nextp wait(empty); wait(mutex); wait(full); wait(mutex); remove an item from buffer place it in nextc add nextp to the buffer signal(mutex); signal(full); until false signal(mutex); signal(empty); consume the item in nextc until false Posix Semaphores Counting semaphores sem_init - creates a unnamed semaphore and initializes it int sem_init(sem_t *sem, int pshared, unsigned int value); sem_open - creates a named semaphore and initializes it sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); sem_wait - performs a wait operation int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); sem_post - performs a signal operation int sem_post(sem_t *sem); #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h> void *functionC(void *ptr); int counter = 0; sem_t sem; This works for Linux main() { int rc1, rc2; pthread_t thread1, thread2; sem_init(&sem, PTHREAD_PROCESS_PRIVATE, 1); // Now it is set to one, one person will be able to access at a time void *functionC(void *ptr) { int tmp; printf("Got semaphore %d\n",sem); sem_wait(&sem); tmp = counter; sleep(1); tmp++; counter = tmp; printf("Counter value: %d\n",counter); sem_post(&sem); /* Create independent threads each of which will execute functionC */ if( (rc1=pthread_create( &thread1, NULL, &functionC, NULL)) ) { printf("Thread creation failed: %d\n", rc1); } if( (rc2=pthread_create( &thread2, NULL, &functionC, NULL)) ) { printf("Thread creation failed: %d\n", rc2); } /* Wait till threads are complete before main continues. Unless we */ /* wait we run the risk of executing an exit which will terminate */ /* the process and all threads before the threads have completed. */ pthread_join( thread1, NULL); pthread_join( thread2, NULL); sem_close(&sem); exit(0); } } ~ #ifdef __APPLE__ #include <mach/semaphore.h> #include <mach/task.h> #include <mach/mach.h> #else #include <semaphore.h> #endif void qsem_create(void * semStructure, int initialValue) { #ifdef __APPLE__ semaphore_create(mach_task_self(), (semaphore_t *)semStructure, SYNC_POLICY_FIFO, initialValue); #else int pshared = 0; sem_init((sem_t *)semStructure, pshared, initialValue); #endif } void qsem_signal(void * semStructure) { #ifdef __APPLE__ semaphore_signal(*((semaphore_t *)semStructure)); #else sem_post((sem_t *)semStructure); #endif } void qsem_wait(void * semStructure) { #ifdef __APPLE__ semaphore_wait(*((semaphore_t *)semStructure)); #else sem_wait((sem_t *)semStructure); #endif } void qsem_close(void * semStructure) { #ifdef __APPLE__ semaphore_destroy(mach_task_self(), *((semaphore_t *)semStructure)); #else sem_close((sem_t *)semStructure); #endif } #ifdef __APPLE__ semaphore_t sem; #else sem_t sem; #endif Unix System V Semaphores Are a generalization of the counting semaphores (more operations are permitted). A semaphore includes: the current value S of the semaphore number of processes waiting for S to increase number of processes waiting for S to be 0 System calls semget creates an array of semaphores semctl allows for the initialization of semaphores semop performs a list of operations: one on each semaphore (atomically) Unix Semaphore Code Creation union semun argument; key_t key = IPC_PRIVATE; int flags = 0777 | IPC_CREAT; #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> void *functionC(void *ptr); int counter = 0; int sem; #define NSEMS 1 #define SEMFLAG (IPC_CREAT| 0666) union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux specific) */ }; int semid = semget(key, 1, flags); argument.val = initialvalue; semctl(semid, 0, SETVAL, argument); Destruction int ignored_int; union semun ignored; semctl(semid, ignored_int, IPC_RMID, ignored); Unix Semaphores Each operation to be done is specified by a value sem_op. Let S be the semaphore value if sem_op > 0: (signal operation) S is incremented and process awaiting for S to increase are awaken if sem_op = 0: If S=0: do nothing if S!=0, block the current process on the event that S=0 if sem_op < 0: (wait operation) if S >= | sem_op | then S = S - | sem_op | then if S <=0 wait Wait and Signal Set up the operations array for 1 semaphore struct sembuf operations[1]; operations[0].sem_num = 0; operations[0].sem_flg = SEM_UNDO; Wait operations[0].sem_op = -1; Signal operations[0].sem_op = 1; Execute the operation on 1 semaphore semop(semid, operations, 1); Semaphores & Interrupts Semaphore operations may be interrupted Will not be restarted Semop will return -1 int sem_wait(int semid) { struct sembuf operations[1]; int retval; operations[0].sem_num = 0; operations[0].sem_op = -1; operations[0].sem_flg = SEM_UNDO; while ((retval = semop(semid, operations, 1)) == -1) { if (errno != EINTR) { fprintf(stderr,"sem_wait error %d\n", errno); exit(4); } } return retval; } Unix Semaphores Operating System level data structure Can be shared among processes Identified by a key and an ID List semaphores $ ipcs Remove zombie semaphores $ ipcrm -s semid Classical Synchronization Problems Counting semaphores from binary semaphores Bounded Buffer Shared buffer between producer and consumer Readers and Writers data object shared between many some read only some write only Dining Philosophers n processes p resources Binary to Counting Semaphores Can you create a counting semaphore from binary semaphores? Needed: Integer count Operations on the count must be atomic Must block processes appropriately How many binary semaphores are needed? Can you do it with just one? Binary to Counting Semaphores Typedef struct qsem { pthread_mutex_t s; int value; } void wait(qsem_t* q) { pthread_mutex_lock(q->s); q->value--; if (value < 0) { // I need to block..... how? } pthread_mutex_unlock(q->s); } void signal(qsem_t* q) { pthread_mutex_lock(q->s); q->value++; if (value <= 0) { // I need to wake someone // ..... how? } pthread_mutex_unlock(q->s); } Binary to Counting Semaphores typedef struct qsem { pthread_mutex_t s1; pthread_mutex_t s2; int value; } void wait(qsem_t* q) { pthread_mutex_lock(&(q->s1)); q->value--; if (value < 0) { pthread_mutex_unlock(&(q->s1)); pthread_mutex_lock(&(q->s2)); } pthread_mutex_unlock(&(q->s1)); } void qsem_create(qsem_t *q, int initialvalue) { pthread_mutex_init(&(q->s1), NULL); pthread_mutex_init(&(q->s2), NULL); // We must initialize s1 to 1(default) // and s2 to 0 (we must lock it) pthread_mutex_lock(&(q->s2)); q->value = initialvalue; } void signal(qsem_t* q) { pthread_mutex_lock(&(q->s1)); q->value++; if (value <= 0) pthread_mutex_unlock(&(q->s2)); else pthread_mutex_unlock(&(q->s1)); } Bounded Buffer Problem x1 x2 x3 … xn P0 P1 Bounded Buffer Solution? repeat … produce an item in nextp … while counter == n do no-op buffer[in] = nextp in = (in + 1) mod n counter = counter + 1 until false repeat … while counter == 0 do no-op nextc = buffer[out] out = (out + 1) mod n counter = counter -1 … consume the item in nextc … until false Bounded Buffer Solution Shared semaphore: empty = n, full = 0, producer_mutex = 1, consumer_mutex = 1; repeat repeat produce an item in nextp wait(empty); wait(producer_mutex); add nextp to the buffer only modify in signal(producer_mutex); signal(full); until false wait(full); wait(consumer_mutex); remove an item from buffer place it in nextc only modify out signal(consumer_mutex); signal(empty); consume the item in nextc until false Note: We don’t need counter any more. Why? The Dining Philosophers Problem 5 philosophers who only eat and think each need to use 2 forks for eating we have only 5 forks Illustrates the difficulty of allocating resources among processes/threads without deadlock and starvation The Dining Philosophers Problem Each philosopher is a process One semaphore per fork: fork: array[0..4] of semaphores Initialization: fork[i].count:=1 for i:=0..4 A first attempt: Process Pi: repeat think; wait(fork[i]); wait(fork[i+1 mod 5]); eat; signal(fork[i+1 mod 5]); signal(fork[i]); forever • Deadlock if each philosopher starts by picking left fork! The Dining Philosophers Problem A solution: admit only 4 philosophers at a time that tries to eat Then 1 philosopher can always eat when the other 3 are holding 1 fork Introduce semaphore T that limits at 4 the numb. of philosophers “sitting at the table” Initialize: T.count:=4 Process Pi: repeat think; wait(T); wait(fork[i]); wait(fork[i+1 mod 5]); eat; signal(fork[i+1 mod 5]); signal(fork[i]); signal(T); forever Other solutions A philosopher may only pick up forks in pairs must allocate all resources at once Asymmetric solution odd philosophers select left then right even philosophers select right then left All solutions must not starve a philosopher Readers and Writers Problem Data object is shared many readers many writers Many can read at the same time Only one writer at a time no reading while writing Many different varieties reader priority writer priority Readers - Writers (priority?) Shared Semaphore mutex=1, wrt = 1; Shared integer readcount = 0; wait(wrt); wait(mutex); readcount = readcount + 1; if (readcount == 1) wait(wrt); signal(mutex); write to the data object read the data signal(wrt); wait(mutex); readcount = readcount - 1; if (readcount == 0) signal(wrt); signal(mutex); outerQ, rsem, rmutex, wmutex, wsem: = 1 Readers – Writers (priority?) wait (outerQ) wait (rsem) wait (rmutex) readcnt++ if (readcnt == 1) wait (wsem) signal(rmutex) signal (rsem) signal (outerQ) READ wait (rmutex) readcnt--; if (readcnt == 0) signal (wsem) signal (rmutex) wait (wsem) writecnt++; if (writecnt == 1) wait (rsem) signal (wsem) wait (wmutex) WRITE signal (wmutex) wait (wsem) writecnt--; if (writecnt == 0) signal (rsem) signal (wsem) The Barbershop Barber chairs Cashier Entrance Standing room area Exit Sofa