An Introduction to Programming with 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 are useful because of real-world parallelism:
input/output devices (flesh or silicon) may be slow but are independent -> overlap IO and computation distributed systems have many computing entities multi-processors/multi-core are becoming more common better resource sharing & utilization then processes
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!
Thread creation
Thread type Fork(proc, args) returns thread Join(thread) returns value
Mutual Exclusion
Mutex type Lock(mutex), a block-structured language construct in this lecture
Condition Variables
Condition type Wait(mutex, condition) Signal(condition) Broadcast(condition)
Fork, Wait, Signal, etc. are not to be confused with the UNIX fork, wait, signal, etc. calls
{
Thread thread1; thread1 = Fork(safe_insert, 4); safe_insert(6);
Join(thread1); // Optional
}
list<int> my_list; Mutex m;
void safe_insert(int i) { Lock(m) { my_list.insert(i); } }
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)
Mutex io_mutex; Condition non_empty; ... Consumer: Lock (io_mutex) { while (port.empty()) Wait(io_mutex, non_empty); process_data(port.first_in()); } Producer: Lock (io_mutex) { port.add_data(); Signal(non_empty); }
Each condition variable is associated with a single mutex Wait atomically unlocks the mutex and blocks the thread Signal awakes a blocked thread
Doesnt this sound complex? Why do we do it?
the idea is that the condition of the condition variable depends on data protected by the mutex
the thread is awoken inside Wait tries to lock the mutex when it (finally) succeeds, it returns from the Wait
Mutex io_mutex; Condition non_empty; ... Consumer: Lock (io_mutex) { while (port.empty()) Wait(io_mutex, non_empty); process_data(port.first_in()); } Producer: Lock (io_mutex) { port.add_data(); Signal(non_empty); }
Mutexes and condition variables serve different purposes
Mutex: exclusive access Condition variable: long waits