Threads and Locks

CS 321 Lecture, Dr. Lawlor, 2006/02/06

Silberschatz chapter 4 describes threads in some detail. Chapter 6 describes synchronization primitives, and the critical section problem.

Thread Calls Across Platforms


Windows
UNIX: Linux, Mac OS X
My porthread library, osl/porthread.h
Create a Thread
CreateThread(...) (example)
pthread_create(&th,0,fn,arg) (example)
porthread th=porthread_create(fn,arg);
Wait for a Thread to Finish
WaitForSingleObject(th,INFINITE)
pthread_join(th,0);
porthread_wait(th);
Create a Lock
InitializeCriticalSection
pthread_mutex_init
porlock lk;
Lock the lock
EnterCriticalSection
pthread_mutex_lock
lk.lock();
Unlock the lock
LeaveCriticalSection
pthread_mutex_unlock
lk.unlock();
Delete the lock
DeleteCriticalSection
pthread_mutex_destroy
(delete variable lk)

A kernel thread is just a fancy way to run one of your subroutines simultaniously with all the other subroutines.  The thread can still access your global variables, or anything it can find belonging to other threads.  This shared access to common variables immediately introduces the many problems of "thread safety".   For example, consider a piece of code like this:
int shared=0;
void inc(void) {
int i=shared;
i++;
shared=i;
}
If two threads try to call "inc" repeatedly, the two executions might interleave like this:
Thread A
Thread B
int i=shared; // i==0
i++; // i==1
   //  hit interrupt.  switch to B












shared=i; // i is still 1, so shared==1!



int i=shared; // i==0 (again)
i++; //i==1
shared=i; // shared==1

int i=shared; // i==1
i++; //i==2
shared=i; // shared==2

int i=shared; // i==2
i++; //i==3
shared=i; // shared==3
// hit interrupt, switch back to A


Uh oh!  When we switch back to thread A, the value stored in "i" isn't right anymore. It's an older copy of a shared global variable, stored in thread A's stack or registers.  So thread A will happily overwrite the shared variable with its old version, causing all of B's work to be lost.

The simplest tool for synchronizing access to common variables is the "lock", which is also called a "mutual exclusion device" (or "mutex") or a "critical section".  This "lock" works exactly like the lock in a bathroom stall.  You walk into the stall.  You don't want anybody else barging in while you use the stall, so you lock the lock before you do your business.  Before you leave, you unlock the lock.  If somebody else wants to use the stall while you've locked it, they have to wait. 

Note that this system breaks down if:
Both these problems are suprisingly common in real multithreaded code!

We can make access to any shared variable atomic by wrapping a lock around the access:
int shared=0;
porlock lk; /* lock for "shared" */
void inc(void) {
lk.lock(); /* now we have exclusive access */
int i=shared;
i++;
shared=i;
lk.unlock(); /* give other threads a chance */
}