Operating System: Three Easy Pieces --- Locks (Note)

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 this pair of routines:

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

The routines should be easy to understand and use. When you have a region of code you realize

is a critical section, and thus needs to to be protected by locks in order to operate as desired. You

can probably imagine what the code looks like:

pthread_mutex_t lock;
pthread_mutex_lock(&lock);
x = x + 1;
pthread_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 improtant ways. The first problem is a lack of proper

initialization. All locks must be properly initialized in order to guarantee that they are 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 at run time is to make a call to pthread_mutex_init(), as follows:

int rc = pthread_mutex_init(&lock, NULL);
assert(rc  == 0);

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 in simple uses

the defaults. Either way works, but we usually use the dynamic latter method. 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 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 succeed; more sophisticated programs, which cann't simple exit when something

goes wrong, should check for failure and do something appropriate when the lock or unlock 

does not succeed.

The lock and unlock routines are not only routines within the pthreads library to interact with

locks. In particular, here are two more routines which may be 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 we'll see in future chapters when we study deadlock.

原文地址:https://www.cnblogs.com/miaoyong/p/4977280.html