Announcements

advertisement
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
Download