13.6 Readers and Writers

Team-FLY

The reader-writer problem refers to a situation in which a resource allows two types of access (reading and writing). One type of access must be granted exclusively (e.g., writing), but the other type may be shared (e.g., reading). For example, any number of processes can read from the same file without difficulty, but only one process should modify the file at a time.

Two common strategies for handling reader-writer synchronization are called strong reader synchronization and strong writer synchronization . Strong reader synchronization always gives preference to readers, granting access to readers as long as a writer is not currently writing. Strong writer synchronization always gives preference to writers, delaying readers until all waiting or active writers complete. An airline reservation system would use strong writer preference, since readers need the most up-to-date information. On the other hand, a library reference database might want to give readers preference.

POSIX provides read-write locks that allow multiple readers to acquire a lock, provided that a writer does not hold the lock. POSIX states that it is up to the implementation whether to allow a reader to acquire a lock if writers are blocked on the lock.

POSIX read-write locks are represented by variables of type pthread_rwlock_t . Programs must initialize pthread_rwlock_t variables before using them for synchronization by calling pthread_rwlock_init . The rwlock parameter is a pointer to a read-write lock. Pass NULL for the attr parameter of pthread_rwlock_init to initialize a read-write lock with the default attributes. Otherwise, first create and initialize a read-write lock attribute object in a manner similar to that used for thread attribute objects.

  SYNOPSIS  #include <pthread.h>   int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,                         const pthread_rwlockattr_t *restrict attr);  POSIX:THR  

If successful, pthread_rwlock_init returns 0. If unsuccessful , it returns a nonzero error code. The following table lists the mandatory errors for pthread_rwlock_init .

error

cause

EAGAIN

system lacked nonmemory resources needed to initialize *rwlock

ENOMEM

system lacked memory resources needed to initialize *rwlock

EPERM

caller does not have appropriate privileges

Exercise 13.25

What happens when you try to initialize a read-write lock that has already been initialized ?

Answer:

POSIX states that the behavior under these circumstances is not defined.

The pthread_rwlock_destroy function destroys the read-write lock referenced by its parameter. The rwlock parameter is a pointer to a read-write lock. A pthread_rwlock_t variable that has been destroyed with pthread_rwlock_destroy can be reinitialized with pthread_rwlock_init .

  SYNOPSIS  #include <pthread.h>   int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);  POSIX:THR  

If successful, pthread_rwlock_destroy returns 0. If unsuccessful, it returns a nonzero error code. No mandatory errors are defined for pthread_rwlock_destroy .

Exercise 13.26

What happens if you reference a read-write lock that has been destroyed?

Answer:

POSIX states that the behavior under these circumstances is not defined.

The pthread_rwlock_rdlock and pthread_rwlock_tryrdlock functions allow a thread to acquire a read-write lock for reading. The pthread_rwlock_wrlock and pthread_rwlock_trywrlock functions allow a thread to acquire a read-write lock for writing. The pthread_rwlock_rdlock and pthread_rwlock_wrlock functions block until the lock is available, whereas pthread_rwlock_tryrdlock and pthread_rwlock_trywrlock return immediately. The pthread_rwlock_unlock function causes the lock to be released. These functions require that a pointer to the lock be passed as a parameter.

  SYNOPSIS  #include <pthread.h>   int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);   int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);   int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);   int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);   int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  POSIX:THR  

If successful, these functions return 0. If unsuccessful, these functions return a nonzero error code. The pthread_rwlock_tryrdlock and pthread_rwlock_trywrlock functions return EBUSY if the lock could not be acquired because it was already held.

Exercise 13.27

What happens if a thread calls pthread_rwlock_rdlock on a lock that it has already acquired with pthread_rwlock_wrlock ?

Answer:

POSIX states that a deadlock may occur. (Implementations are free to detect a deadlock and return an error, but they are not required to do so.)

Exercise 13.28

What happens if a thread calls pthread_rwlock_rdlock on a lock that it has already acquired with pthread_rwlock_rdlock ?

Answer:

A thread may hold multiple concurrent read locks on the same read-write lock. It should make sure to match the number of unlock calls with the number of lock calls to release the lock.

Program 13.16 uses read-write locks to implement a thread-safe wrapper for the list object of Program 2.7. The listlib.c module should be included in this file, and its functions should be qualified with the static attribute. Program 13.16 includes an initialize_r function to initialize the read-write lock, since no static initialization is available. This function uses pthread_once to make sure that the read-write lock is initialized only one time.

Exercise 13.29

Compare Program 13.16 to the thread-safe implementation of Program 13.9 that uses mutex locks. What are the advantages/disadvantages of each?

Answer:

The mutex is a low-overhead synchronization mechanism. Since each of the functions in Program 13.9 holds the listlock only for a short period of time, Program 13.9 is relatively efficient. Because read-write locks have some overhead, their advantage comes when the actual read operations take a considerable amount of time (such as incurred by accessing a disk). In such a case, the strictly serial execution order would be inefficient.

Program 13.16 listlibrw_r.c

The list object of Program 2.7 synchronized with read-write locks .

 #include <errno.h> #include <pthread.h> static pthread_rwlock_t listlock; static int lockiniterror = 0; static pthread_once_t lockisinitialized = PTHREAD_ONCE_INIT; static void ilock(void) {    lockiniterror = pthread_rwlock_init(&listlock, NULL); } int initialize_r(void) {    /* must be called at least once before using list */    if (pthread_once(&lockisinitialized, ilock))       lockiniterror = EINVAL;    return lockiniterror; } int accessdata_r(void) {               /* get a nonnegative key if successful */    int error;    int errorkey = 0;    int key;    if (error = pthread_rwlock_wrlock(&listlock)) {  /* no write lock, give up */       errno = error;       return -1;    }    key = accessdata();    if (key == -1) {       errorkey = errno;       pthread_rwlock_unlock(&listlock);       errno = errorkey;       return -1;    }    if (error = pthread_rwlock_unlock(&listlock)) {       errno = error;       return -1;    }    return key; } int adddata_r(data_t data) {          /* allocate a node on list to hold data */    int error;    if (error = pthread_rwlock_wrlock(&listlock)) { /* no writer lock, give up */       errno = error;       return -1;    }    if (adddata(data) == -1) {       error = errno;       pthread_rwlock_unlock(&listlock);       errno = error;       return -1;    }    if (error = pthread_rwlock_unlock(&listlock)) {       errno = error;       return -1;    }    return 0; } int getdata_r(int key, data_t *datap) {               /* retrieve node by key */    int error;    if (error = pthread_rwlock_rdlock(&listlock)) { /* no reader lock, give up */       errno = error;       return -1;    }    if (getdata(key, datap) == -1) {       error = errno;       pthread_rwlock_unlock(&listlock);       errno = error;       return -1;    }    if (error = pthread_rwlock_unlock(&listlock)) {       errno = error;       return -1;    }    return 0; } int freekey_r(int key) {                                      /* free the key */    int error;    if (error = pthread_rwlock_wrlock(&listlock)) {       errno = error;       return -1;    }    if (freekey(key) == -1) {       error = errno;       pthread_rwlock_unlock(&listlock);       errno = error;       return -1;    }    if (error = pthread_rwlock_unlock(&listlock)) {       errno = error;       return -1;    }    return 0; } 
Exercise 13.30

The use of Program 13.16 requires a call to initialize_r at least once by some thread before any threads call other functions in this library. How could this be avoided?

Answer:

The function initialize_r can be given internal linkage by having the other functions in the library call it before accessing the lock.

Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net