An Introduction To Programming With Threads
An Introduction To Programming With Threads
Resources
Birrell - "An Introduction to Programming with Threads" Silberschatz et al., 7th ed, Chapter 4
Threads
A thread is a single sequential flow of control
A process can have many threads and a single address space Threads share memory and, hence, need to cooperate to produce correct results Thread has thread specific data (registers, stack pointer, program counter)
Threads continued..
Thread Mechanisms
Birrell identifies four mechanisms used in threading systems:
thread creation mutual exclusion waiting for events interrupting a threads wait
In most mechanisms in current use, only the first three are covered In the paper - primitives used abstract, not derived from actual threading system or programming language!
Mutual Exclusion
Mutex type Lock(mutex), a block-structured language construct in this lecture
Fork, Wait, Signal, etc. are not to be confused with the UNIX fork, wait, signal, etc. calls
Creation Example
{ Thread thread1; thread1 = Fork(safe_insert, 4); safe_insert(6);
Join(thread1); // Optional
}
Mutex Example
list<int> my_list; Mutex m; void safe_insert(int i) { Lock(m) { my_list.insert(i); } }
Condition Variables
Mutexes are used to control access to shared data only one thread can execute inside a Lock clause other threads who try to Lock, are blocked until the mutex is unlocked Condition variables are used to wait for specific events free memory is getting low, wake up the garbage collector thread 10,000 clock ticks have elapsed, update that window new data arrived in the I/O port, process it Could we do the same with mutexes? (think about it and well get back to it)
Whats wrong with this? What if we dont lock the mutex (or unlock it before going to sleep)?
Question: Isnt it weird to have both mutexes and condition variables? Couldnt a single mechanism suffice? Answer:
What happens if this code is run in two different threads with no mutual exclusion?
Why use while instead of if? (think of many consumers, simplicity of coding producer)
Readers/Writers Locking
Mutex counter_mutex; Condition read_phase, write_phase; int readers = 0; Reader: Lock(counter_mutex) { while (readers == -1) Wait(counter_mutex, read_phase); readers++; } ... //read data Lock(counter_mutex) { readers--; if (readers == 0) Signal(write_phase); }
Writer: Lock(counter_mutex) { while (readers != 0) Wait(counter_mutex, write_phase); readers = -1; } ... //write data Lock(counter_mutex) { readers = 0; Broadcast(read_phase); Signal(write_phase); }
Readers/Writers Example
Reader:
Lock(mutex) { while (writer) Wait(mutex, read_phase) readers++; } // read data Lock(mutex) { readers--; if (readers == 0) Signal(write_phase); }
Writer:
Lock(mutex) { while (readers !=0 || writer) Wait(mutex, write_phase) writer = true; } // write data Lock(mutex) { writer = false; Broadcast(read_phase); Signal(write_phase); }
To Do: Think about avoiding the problem of waking up readers that will contend for a single mutex if executed on multiple processors
Discussion Example
Two kinds of threads, red and green, are accessing a critical section. The critical section may be accessed by at most three threads of any kind at a time. The red threads have priority over the green threads.
Discuss the CS_enter and CS_exit code. Why do we need the red_waiting variable? Which condition variable should be signalled when? Can we have only one condition variable?
Red: Lock(m) { // ??? while (green + red == 3) Wait(m, red_cond); red++; // ??? } ... //access data Lock(m) { red--; // ??? // ??? // ??? }
Green: Lock(m) { while (green + red == 3 || red_waiting != 0) Wait(m, green_cond); green++; } ... //access data Lock(mutex) { green--; // ??? // ??? // ??? }
Deadlocks (brief)
Well talk more later for now beware of deadlocks Examples: A locks M1, B locks M2, A blocks on M2, B blocks on M1 Similar examples with condition variables and mutexes Techniques for avoiding deadlocks: Fine grained locking Two-phase locking: acquire all the locks youll ever need up front, release all locks if you fail to acquire any one very good technique for some applications, but generally too restrictive Order locks and acquire them in order (e.g., all threads first acquire M1, then M2)
Issue: mutex maybe be locked by parent at fork time -> child process will get its own copy of mutex with state LOCKED and noone to unlock it!
Scope of multithreading
LWPs, kernel and user threads kernel-level threads supported by the kernel
Solaris, Linux, Windows XP/2000 all scheduling, synchronization, thread structures maintained in kernel could write apps using kernel threads, but would have to go to kernel for everything