Locks
The mutual exclusion is a very important utility needed in multithreaded programs. You will study locks in this lesson, that serve this purpose.
We'll cover the following
Beyond thread creation and join, probably the next most useful set of functions provided by the POSIX threads library are those for providing mutual exclusion to a critical section via locks. The most basic pair of routines to use for this purpose is provided by the following:
int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);
Lock and unlock routines
The routines should be easy to understand and use. When you have a region of code that is a critical section and thus needs to be protected to ensure correct operation, locks are quite useful. You can probably imagine what the code looks like:
pthread_mutex_t lock;pthread_mutex_lock(&lock);x = x + 1; // or whatever your critical section ispthread_mutex_unlock(&lock);
The intent of the code is as follows: if no other thread holds the lock when pthread_mutex_lock()
is called, the thread will acquire the lock and enter the critical section. If another thread does indeed hold the lock, the thread trying to grab the lock will not return from the call until it has acquired the lock (implying that the thread holding the lock has released it via the unlock call). Of course, many threads may be stuck waiting inside the lock acquisition function at a given time; only the thread with the lock acquired, however, should call unlock.
Unfortunately, this code is broken, in two important ways. The first problem is the lack of proper initialization. All locks must be properly initialized in order to guarantee that they have the correct values, to begin with, and thus work as desired when lock and unlock are called.
With POSIX threads, there are two ways to initialize locks. One way to do this is to use PTHREAD_MUTEX_INITIALIZER
, as follows:
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
Doing so sets the lock to the default values and thus makes the lock usable. The dynamic way to do it (i.e., at run time) is to make a call to pthread_mutex_init()
, as follows:
int rc = pthread_mutex_init(&lock, NULL);assert(rc == 0); // always check success!
The first argument to this routine is the address of the lock itself, whereas the second is an optional set of attributes. Read more about the attributes yourself; passing NULL
simply uses the defaults. Either way works, but usually the dynamic (latter) method is used. Note that a corresponding call to pthread_mutex_destroy()
should also be made, when you are done with the lock; see the manual page for all of the details.
The second problem with the code above is that it fails to check error codes when calling lock and unlock. Just like virtually any library routine you call in a UNIX system, these routines can also fail! If your code doesn’t properly check error codes, the failure will happen silently, which in this case could allow multiple threads into a critical section. Minimally, use wrappers, which assert that the routine succeeded, as shown in the code excerpt below; more sophisticated (non-toy) programs, which can’t simply exit when something goes wrong, should check for failure and do something appropriate when a call does not succeed.
// Keeps code clean; only use if exit() OK upon failurevoid Pthread_mutex_lock(pthread_mutex_t *mutex) {int rc = pthread_mutex_lock(mutex);assert(rc == 0);}
Lock acquisition routines
The lock and unlock routines are not the only routines within the pthreads library to interact with locks. Two other routines of interest:
int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_timedlock(pthread_mutex_t *mutex,struct timespec *abs_timeout);
These two calls are used in lock acquisition. The trylock
version returns failure if the lock is already held; the timedlock
version of acquiring a lock returns after a timeout or after acquiring the lock, whichever happens first. Thus, the timedlock with a timeout of zero degenerates to the trylock case. Both of these versions should generally be avoided; however, there are a few cases where avoiding getting stuck (perhaps indefinitely) in a lock acquisition routine can be useful, as you’ll see in future chapters (e.g., when you study deadlock).
Get hands-on with 1400+ tech skills courses.