Title: Assignment 2
1Assignment 2
Thread Synchronization
http//www.2pass.co.uk/roundabout.htm
2Roundabouts
3Negotiating the Roundabout
4Traffic Circle Simulation
5Pthreads
Thread Synchronization
http//dis.cs.umass.edu/wagner/threads_html/tutor
ial.html http//www.fsmlabs.com/developers/docs/ht
ml/susv2/xsh/pthread.h.html
Silberchatz et al, Operating System Concepts,
Section 7.4 A. Tenanbaum, Modern Operating
Systems 2nd Ed., Section 2.3
6Hello World
Main thread
void print_message_function( void ptr )
main() pthread_t thread1, thread2 char
message1 "Hello" char message2
"World" pthread_create( thread1,
pthread_attr_default,
(void)print_message_function, (void)
message1) pthread_create(thread2,
pthread_attr_default,
(void)print_message_function, (void)
message2) exit(0)
Page 1
7Hello World
Print message function
void print_message_function( void ptr )
char message message (char )
ptr printf("s ", message)
Page 2
8Race Conditions
- One thread prints Hello, the other prints
World. - Two flaws
- Threads execute concurrently, so we may see
either World Hello or Hello World. - If the main thread executes exit() before the
printer threads finish, then no output will be
generated. - Can be fixed with duct tape delays
9Hello World
Main thread
void print_message_function( void ptr )
main() pthread_t thread1, thread2 char
message1 "Hello" char message2 "World"
pthread_create( thread1,
pthread_attr_default, (void )
print_message_function, (void ) message1)
sleep(10) pthread_create(thread2,
pthread_attr_default, (void )
print_message_function, (void ) message2)
sleep(10) exit(0)
Page 2
10Hello World
Print message function
void print_message_function( void ptr )
char message message (char ) ptr
printf("s", message) pthread_exit(0)
Page 2
11Race Conditions and Sleep
- It is never safe to rely on timing delays to
perform synchronization. - Two processes are competing for a resource
(stdio) - If the process were distributed, the time allowed
may be insufficient. - When a thread calls sleep, the entire process
sleeps (i.e. all threads in the process sleep) - The same race condition exists, but 20 seconds
longer - Use pthread_delay_np() (?)
12Delaying a Thread
- To delay a thread for two seconds (???)
struct timespec delay delay.tv_sec
2 delay.tv_nsec 0 pthread_delay_np( delay )
- Possible in DEC OSF/1 OS, V 3.0 POSIX, but not
under POSIX 1003.1C
- The closest that we have is pthread_cond_timedwait
()
13Race Condition
- A race condition occurs when two or more
processes or threads access and manipulate the
same data concurrently, and - the outcome of the execution depends on the
particular order in which the access takes place. - To ensure that only one process at a time
accesses the data, we require some form of
synchronization of the processes.
14Mutual Exclusion
- One way to avoid a race condition is to identify
critical sections of the code, and ensure mutual
exclusion of the two threads or processes.
Critical regions
Task A
Task B
B attempts to enter its critical region
B blocked
15Mutual Exclusion
- Four conditions for a good solution
- No two processes may be simultaneously inside
their critical regions. - No assumptions may be made about speeds or the
number of CPUs - No process running outside its critical region
may block other processes. - No process should have to wait forever to enter
its critical region.
16Lock Variable
- A simple lock variable wont ensure mutual
exclusion.
Lock check interrupted
Critical regions then interrupted
Task A
Task B
17Lock Variable?
- We could define a lock variable, Lock
- If Task A wants to run, it checks Lock
- If Lock 0, then no process is in its critical
region - if Lock 1, then some process is in its critical
region. - Task A sets Lock 1, enters its critical region,
then finishes and sets Lock 0. - But Task B could interrupt Task A while it is
reading the Lock. If Task A sees that Lock 0,
then is bumped by Task B which begins running,
then Task A starts running again, then both are
in their critical regions at the same time.
18Strict Alteration
Process B
Process A
while (TRUE) while(turn ! 1) / loop
/ critical_region() turn 0
noncritical_region()
while (TRUE) while(turn ! 0) / loop
/ critical_region() turn 1
noncritical_region()
Page 2
19Strict Alteration
- An integer variable turn keeps track of whos
turn it is - When turn 0, its time for Process A
- When turn 1, its time for Process B
- Continually testing a variable until some value
appears is called busy waiting - A lock that uses busy waiting is called a spin
lock
20Test and Set Lock (TSL)
- Many computers have an instruction
- TSL RX,LOCK
- Read the contents of memory word lock into
register RX - Store a non-zero value at memory address lock.
- The operations of reading the word and storing
into it are guaranteed to be indivisible. - In a multi-CPU system, the memory bus is locked
to prevent other CPUs from accessing memory until
the TSL is complete
21Using Test and Set Lock
Enter critical region
Copy lock to register and set lock to 1 Was lock
0? If it was non-zero, lock was set, so loop If
zero, return to caller, enter critical
region
enter_region TSL REGISTER,LOCK CMP
REGISTER,0 JNE enter_region RET
Leave critical region
Leave_region MOVE LOCK,0 RET
Store a 0 in lock
Page 2
22Mutex
- A mutex is a variable that can be in two states,
locked and unlocked. - When a thread or process needs access to a
critical region, it calls mutex_lock. - If the mutex is currently unlocked, the call
succeeds and the calling thread is free to enter
the critical region. - If the mutex is currently locked, the calling
thread is blocked until the thread in the
critical region is finished and calls
mutex_unlock.
23Code for Mutex
Mutex_lock
Mutex_lock TSL REGISTER,MUTEX CMP
REGISTER,0 JZE ok CALL thread_yield
JMP mutex_lock Ok RET
Copy mutex to register and set mutex to 1 Was
lock 0? If it was zero, mutex unlocked, so
return If non-zero, mutex busy schedule another
thread Try again later Return to caller enter
critical region
Mutex_unlock
Mutex_unlock MOVE MUTEX,0 RET
Store a 0 in mutex Return to caller
24Code for Mutex
- Threads will go on indefinitely if in a loop
processes are eventually timed out. - When mutex_lock fails, it calls thread_yield to
give up the CPU to another thread. Hence, there
is no busy waiting. - Neither mutex_lock nor mutex_unlock require
kernel calls. Thread_yield is a fast call to
thread scheduler in user space.
25Using Mutex
Thread 2
Thread 1
mutex_lock(m) Local global mutex_unlock(m)
mutex_lock(m) global mutex_unlock(m)
- Mutex is the simplest and most primitive
synchronization variable. - It provides a single, absolute owner for the
section of code between mutex_lock() and
mutex_unlock()
26Mutex with Three Threads
- Threads T1, T2, T3 request a mutex one after the
other. - Their priority levels are T1,0 T2,1 T3,2
- T1 gets the mutex, T2, T3 go onto the sleep queue
in their order of priority - When T1 releases the mutex, T3 will be awakened
to claim the mutex
27Mutex with Three Threads
28Condition Variables
Acquire mutex Test condition False! Release
mutex Sleep on CV
Acquire mutex Change condition Signal
T1 Release mutex
Reacquire mutex Retest condition Success!
Do thing Release mutex
29Condition Variables
- A condition can be complex
- e.g. X gt 17 and Y is prime
- CVs always have an associated mutex
- Procedure
- A thread obtains the associated mutex
- Test condition under protection of mutex
- If condition true, complete task, release mutex
- If condition false, mutex is released for you,
thread goes to sleep on the CV - When another thread changes the condition, it
calls cond_signal() to wake up the thread, regain
mutex, retest the condition, and act according to
condition.
30Using a Condition Variable
Thread 2
Thread 1
mutex_lock(m) while (!my_condition) while(cond_w
ait(c, m) ! 0)
mutex_lock(m) my_condition TRUE cond_signal(
c) mutex_unlock(m)
do_thing() mutex_unlock(m)
31Using Condition Variables
- A thread can wake up all sleepers with
cond_broadcast() - A thread can have limited sleep time by calling
cond_timedwait() - When timer expires, cond_timedwait() returns
ETIME - Both cond_wait() and cond_timed() return without
holding the mutex - (hence extra while loop in Thread 1)
32Pthreads and Solaris Threads
- pthread_mutex_t
- pthread_cond_t
- pthread_t
- pthread_create()
- pthread_destroy()
- pthread_exit()
- pthread_join()
- pthread_mutex_init()
- pthread_mutex_destroy()
- pthread_mutex_lock()
- pthread_mutex_unlock()
- pthread_cond_init()
- pthread_cond_destroy()
- pthread_cond_wait()
- pthread_cond_broadcast()
- pthread_cond_signal()
- mutex_t
- cond_t
- thread_t
- thr_create()
- thr_exit()
- thr_join()
- mutex_init()
- mutex_destroy()
- mutex_lock()
- mutex_unlock()
- cond_init()
- cond_destroy()
- cond_wait()
- cond_broadcast()
- cond_signal()
33Producer/Consumer Example
- Producer adds items to a buffer, consumer removes
items from the buffer - Structure Buf holds the buffered data and the
condition variables - When the producer wants to add data, it checks to
see if the buffer is full - If full, the producer blocks on cond_wait()
- When the consumer removes an item, the buffer is
no longer full, so the producer is awakened from
the cond_wait() call. - If empty, the consumer blocks on cond_wait()
- When the producer adds an item, the consumers
condition is satisfied, so it can remove an item
from the buffer
34Producer/Consumer Example
Variable Initialization
define _REEENTRANT include ltstdio.hgt include
ltthread.hgt include ltfcntl.hgt include
ltunistd.hgt include ltsys/stat.hgt include
ltsys/types.hgt include ltsys/uio.hgt define
BUFSIZE 512 define BUFCNT 4
Page 1
35Producer/Consumer Example
Variable initialization
/ this is the data structure that is used
between the producer and consumer threads
/ struct char bufferBUFCNTBUFSIZE
int byteinbufBUFCNT mutex_t buflock
mutex_t donelock cond_t adddata cond_t
remdata int nextadd, nextrem, occ, done
Buf / function prototype / void
consumer(void )
Page 2
36Producer/Consumer Example
Main thread
main(int argc, char argv) int ifd,
ofd thread_t cons_thr / check the command line
arguments / if (argc ! 3) printf("Usage s
ltinfilegt ltoutfilegt\n", argv0) exit(O) /
open the input file for the producer to use / if
((ifd - open(argvl, 0_RDONLY)) -1)
fprintf(stderr, "Can't open file s\n",
argvl) exit(1)
Page 3
37Producer/Consumer Example
Main thread
/ open the output file for the consumer to use
/ if ((ofd open(argv2, 0_WRONLY0_CREAT,
0666)) -1) fprintf(stderr, "Can't open
file s\n", argv2) exit(1)
Page 4
38Producer/Consumer Example
Main thread
/ zero the counters / Buf.nextadd Buf.nextrem
Buf.occ Buf.done 0 / set the thread
concurrency to 2 so the producer and consumer can
run concurrently / thr_setconcurrency(2) /
create the consumer thread / thr_create(NULL, 0,
consumer, (void )ofd, NULL, cons_thr)
Page 5
39Producer/Consumer Example
Main thread - producer
/ the producer ! / while (1) / lock the
mutex / mutex_lock(Buf.buflock) / check
to see if any buffers are empty / / If not
then wait for that condition to become true /
while (Buf.occ BUFCNT) cond_wait(Buf.remd
ata, Buf.buflock) / read from the file and
put data into a buffer / Buf.byteinbufBuf.nex
tadd read(ifd,Buf.bufferBuf.nextadd,BUFS
IZE)
Page 6
40Producer/Consumer Example
Main thread - producer
/ check to see if done reading / if
(Buf.byteinbufBuf.nextadd 0) / lock
the done lock / mutex_lock(Buf.donelock)
/ set the done flag and release the
mutex lock / Buf.done 1
mutex_unlock(Buf.donelock) / signal the
consumer to start consuming /
cond_signal(Buf.adddata) / release the
buffer mutex /' mutex_unlock(Buf-buflock)
/ leave the while loop / break
Page 7
41Producer/Consumer Example
Main thread - producer
/ set the next buffer to fill / Buf.nextadd
Buf.nextadd BUFCNT / increment the
number of buffers that are filled /
Buf.occ / signal the consumer to start
consuming / cond_signal(Buf.adddata) /
release the mutex / mutex_unlock(Buf.buflock)
Page 8
42Producer/Consumer Example
Main thread - producer
close(ifd) / wait for the consumer to
finish / thr_join(cons_thr, 0, NULL) / exit
the program / return(0)
Page 9
43Producer/Consumer Example
Consumer thread
/ The consumer thread / void consumer(void
arg) int fd (int) arg / check to see if
any buffers are filled or if the done flag is set
/ while (1) / lock the mutex /
mutex_lock(Buf.buflock) if (!Buf.occ
Buf.done) mutex_unlock(Buf.buflock)
break
Page 10
44Producer/Consumer Example
Consumer thread
/ check to see if any buffers are filled /
/ if not then wait for the condition to become
true / while (Buf.occ 0 !Buf.done)
cond_wait(Buf.adddata, Buf.buflock) /
write the data from the buffer to the file /
write(fd, Buf.bufferBuf.nextrem,
Buf.byteinbufBuf.nextrem) / set the next
buffer to write from / Buf.nextrem
Buf.nextrem BUFCNT / decrement the
number of buffers that are full / Buf.occ--
Page 11
45Producer/Consumer Example
Consumer thread
/ signal the producer that a buffer is empty
/ cond_signal(Buf.remdata) / release
the mutex / mutex_unlock(Buf.buflock)
/ exit the thread / thr_exit((void )0)
Page 12