PThreads: Semi-FAQ Revivison 5
Search
Google
Web
cognitus.net

POSIX Threads: Semi-FAQ Revision 5.2

© 2001-2006 Michael M. Lampkin
email: michael.lampkin<at>ieee.org

10. POSIX READ-WRITE LOCKS

POSIX supplies the normal mutex type to provide complete locking on a critical section of code. Using a normal mutex, when a thread obtains the mutex all other threads are forced to block until that mutex is released by the owner.

What about the situation where the vast majority of threads are simply reading the data? If this is the case then we should not care if there is 1 or up to N readers in the critical section at the same time. In fact the only time we would normally care about exclusive ownership is when a writer needs access to the code section.

Fortunately, POSIX provides yet another type of mutex called the read-write lock or rwlock that helps improve the efficiency of such code. A ( simplified ) example of a thread's behavior when attempting to obtain possession of the rwlock is:

* thread is a reader

* thread attempts to enter critical section

* no thread(s) in critical section, give access

* if current thread in critical section is a writer, then block

* if current thread(s) in critical section are reader(s), give access

and:

* thread is a writer

* thread attempts to enter critical section

* no thread(s) in critical section, give access

* if current thread in critical section is a writer, then block

* if current thread(s) in critical section are reader(s), then block

There is also the case where we can end up with a combination of readers and writers queued waiting to get ownership of the lock. Will the readers or the writers be given preference for access, will the threads follow a FIFO ordering or is there some other method to determine the proper ordering? The truth of the matter is that the POSIX specification says little about this and it is implementation specific. The result is that you really need to know how the target system implements the rwlock to know the answer.

Having said that there is one follow up. Most implementations seem to make the reasonable conclusion that if you are going to be employing rwlocks then the majority of the threads will be readers. This means that if more than one readers and writer(s) are queued against a lock the writer(s) will be given priority.

You should also see the section on reader starvation.

10.1 How do I determine if my system supports read-write locks?

If your system provides an implementation of POSIX threading then it should also be providing the definitions and functions for rwlock creation, manipulation and destruction.

If a system defines the symbolic constant _POSIX_THREADS in the file unistd.h to a value greater than 0, it provides an implementation of POSIX threading.

10.2 How do I create a read-write lock?

A mutex is of type pthread_mutex_t so the first step of creating an object of this type for use is to include a declaration in your application source code as follows:

pthread_rwlock_t [RWLOCK_VARIABLE_NAME];

Even though the rwlock object has been declared and will be instantiated on execution of the given code, it is still not ready for use. Prior to the first use of the rwlock it must be initialized by calling the function:

int pthread_rwlock_init
(
  pthread_rwlock_t * restrict rwlock,
  const pthread_rwlockattr_t * restrict attr
);

Where the rwlock parameter specifies a pointer to the rwlock we are going to initialize and the attr specifies the characteristics of the lock. If the attr parameter is NULL, the default attributes are used by the system. See the next few sections for details about the pthread_rwlockattr_t type, its creation and the manipulation of the allowed attributes.

This function can fail and return an error number in a variety of situations. Some examples are ENOMEM if system memory is exhausted, EAGAIN if some other system resource is exhausted and EINVAL if an attempt is made to init a rwlock that was previously initialized but not yet destroyed.

On success the function will return a value of zero, the mutex will be initialized and in an unlocked state.

Unlike a standard mutex, there is no static initializer specified by POSIX for read-write locks.

10.3 How many read-write locks can exist at the same time?

There is no means to determine the maximum number of rwlocks that may exist at the same time.

A suggestion would be to find the number of allowed mutexes and then divide that value by two. The reason for the recommended division of that value is because many system implementations use two or more lower level mutex objects to give rwlock functionality.

10.4 How do I destroy a read-write lock?

When a read-write lock is no longer needed it should be destroyed so that any allocated system resource many be freed for reuse. The function which provides rwlock destruction is:

int pthread_rwlock_destroy
(
   pthread_rwlock_t * rwlock
);

The function will return a zero on success. A value of EBUSY is returned if the mutex is currently in use and a value of EINVAL if the mutex is invalid.

10.5 What is rwlock attribute?

The rwlock attribute allows you to modify the behavior of the created read-write lock. The rwlock attribute data type is defined in the file pthread.h and specified to be pthread_rwlockattr_t.

10.6 How do I destroy a rwlock attribute?

When a rwlock attribute is no longer required, it should be explicitly destroyed to free up any resources which may have been allocated to it. The function provided for destruction of rwlock attribute objects is:

int pthread_rwlockattr_destroy
(
   pthread_rwlockattr_t * attr
);

Failure to destroy rwlock attribute objects which are no longer required may result in memory leakage and erratic program execution.

10.7 What are the available rwlock attributes?

Unlike a normal mutex which has many attributes which can be set and manipulated, a rwlock on has:

Process Shared

Details of this attribute setting is described in the next sections.

10.8 What is the rwlock Process Shared Attribute?

The default for this attribute is PTHREAD_PROCESS_PRIVATE and indicates that only those threads within the process which created the read-write lock are allowed to manipulate it.

On the hand, if the attribute has been set to PTHREAD_PROCESS_SHARED, then the rwlock may have operations performed on it by any thread that has access to the memory where the rwlock is allocated. This is the case even if the other threads were created and contained by a process other than the one which created the rwlock.

It a a read write lock which has been created with the attribute PTHREAD_PROCESS_PRIVATE is accessed from another process the result of any such operation is undefined.

10.9 How can I determine if a system supports the rwlock Shared Attribute?

If the symbolic constant _POSIX_THREAD_PROCESS_SHARED is defined in unistd.h to a value greater than zero, the system provides support of the Shared Attribute.

10.10 What functions get/set the rwlock Shared Attribute?

To query a rwlock attribute variable for the current state of its Process Shared Attribute, use the function:

int pthread_rwlockattr_getpshared
(
   const pthread_rwlockattr_t * attr,
   int * pshared
);

Where the parameter attr is a pointer to the location of the rwlock attribute object we want to query and pshared is a pointer to the location where the obtained result, which should be equal to PTHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED, will be placed.

To set the Process Shared Attribute of a rwlock attribute variable, the following function is supplied:

int pthread_rwlockattr_setpshared
(
   const pthread_rwlockattr_t * attr,
   int pshared
);

Where the parameter attr is a pointer to the rwlock attribute object you wish to modify and the parameter pshared is the value PTHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED.

Both functions return a 0 if the operation was successful. They may also return an EINVAL if an invalid parameter is supplied.

10.11 How do I find out if a rwlock is process private or shared?

There is no supplied function to find out if the rwlock is process private or shared once the rwlock has been created. The only options are to implicitly know this information or to save the unmodified rwlock attribute used during the creation.

10.12 How do I lock and unlock a read-write lock?

For a thread to unlock a rwlock which it has obtained for either reading OR writing there is one function supplied defined as follows:

int pthread_rwlock_unlock
(
  pthread_rwlock_t * rwlock
);

Where the parameter rwlock is a rwlock which is currently being held by the calling thread.

On success a zero is returned to the caller. On the other hand if the rwlock parameter is invalid the function will return an EINVAL and if it is not held by the calling thread it will return an EPERM error.

The functions to obtain a rwlock depends on whether the calling thread will be performing a read operation or a write operation. For a thread which desires read only access the functions are:

int pthread_rwlock_rdlock
(
  pthread_rwlock_t * rwlock
);

int pthread_rwlock_timedrdlock
(
  pthread_rwlock_t * restrict rwlock,
  const struct timespec * restrict abs_timeout
);

int pthread_rwlock_tryrdlock
(
  pthread_rwlock_t * rwlock
);

For threads which need exclusive access so that they may safely perform write operations, the functions are:

int pthread_rwlock_wrlock
(
  pthread_rwlock_t * rwlock
);

int pthread_rwlock_timedwrlock
(
  pthread_rwlock_t * restrict rwlock,
  const struct timespec * restrict abs_timeout
);

int pthread_rwlock_trywrlock
(
  pthread_rwlock_t * rwlock
);

These six functions are covered in more detail in the next sections.

10.13 How do I request a lock for reading on a rwlock?

To obtain a reader lock on a rwlock a thread you use the function:

int pthread_rwlock_rdlock
(
  pthread_rwlock_t * rwlock
)

Where the parameter rwlock points to the rwlock the thread is trying to obtain. The function will not return until the thread has obtained ownership of the indicated rwlock or an error conditions occurs.

When the function succeeds it will return a zero. It may also fail and return an EINVAL if the given rwlock is invalid. Additionally if the calling thread already owns the rwlock and the implementation does not allow recursive reader locks then a EDEADLK will be returned to the caller.

Last but certainly not least, the system may have an implementation dependent value for the maximum number of readers allowed to hold the rwlock at the same time. If this value is exceeded then it is also considered an error condition and the return value will be EAGAIN.

10.14 Can I limit the time waiting for a read lock on a rwlock?

If the symbolic constant _POSIX_TIMEOUTS is defined in unistd.h to a value greater than 0 (preferably 200112L) then the system provides the following function:

int pthread_rwlock_timedrdlock
(
  pthread_rwlock_t * restrict rwlock,
  const struct timespec * restrict abs_timeout
);

This function is identical in behavior to pthread_rwlock_rdlock( ) except that when the calling thread would block it will then wait only until the time specified by abs_timeout to gain ownership. If the expiration time is reached without the thread being allowed read access, then the function will return the error ETIMEDOUT.

Note: How abs_timeout is interpreted depends on the resolution of the clock used by the system. From experience and due to the widely varying resolutions employed on available systems, I personally do not rely on any granularity higher than second intervals.

10.15 Can I check if a rwlock would block a reader thread?

To test if a thread wanting reader access to a rwlock would block you can use the function:

int pthread_rwlock_tryrdlock
(
  pthread_rwlock_t * rwlock
);

If the calling thread can immediately obtain the lock then it will do so and the function will return a value of zero. If the calling thread would block or it already owns the rwlock then this function will return a value of EBUSY. It can also return an EINVAL if the rwlock parameter is invalid for any reason.

It should also be mentioned that like pthread_rwlock_rdlock, this function can return EAGAIN if the implementation dependent value for the maximum number allowed readers holding the lock has already been reached.

10.16 How do I request a lock for writing on a rwlock?

To obtain a writer lock on a rwlock a thread you use the function:

int pthread_rwlock_wrlock
(
  pthread_rwlock_t * rwlock
)

Where the parameter rwlock points to the rwlock the thread is trying to obtain. The function will not return until the thread has obtained ownership of the indicated rwlock or an error conditions occurs.

When the function succeeds it will return a zero. It may also fail and return an EINVAL if the given rwlock is invalid. Additionally if the calling thread already owns the rwlock a EDEADLK will be returned to the caller.

Since only one writer may own the rwlock at one time this function will never return a EAGAIN. This behavior is different from what is can happen during reader lock calls with the pthread_rwlock_rdlock and associated functions.

10.17 Can I check if a rwlock would block a writer thread?

To test if a thread wanting writer access to a rwlock would block you can use the function:

int pthread_rwlock_trywrlock
(
  pthread_rwlock_t * rwlock
);

If the calling thread can immediately obtain the lock then it will do so and the function will return a value of zero. If the calling thread would block or it already owns the rwlock then this function will return a value of EBUSY. It can also return an EINVAL if the rwlock parameter is invalid for any reason.

10.18 Can I limit the time waiting for a write lock on a rwlock?

If the symbolic constant _POSIX_TIMEOUTS is defined in unistd.h to a value greater than 0 (preferably 200112L) then the system provides the following function:

int pthread_rwlock_timedwrlock
(
  pthread_rwlock_t * restrict rwlock,
  const struct timespec * restrict abs_timeout
);

This function is identical in behavior to pthread_rwlock_wrlock( ) except that when the calling thread would block it will then wait only until the time specified by abs_timeout to gain ownership. If the expiration time is reached without the thread being allowed write access, then the function will return the error ETIMEDOUT.

Note: How abs_timeout is interpreted depends on the resolution of the clock used by the system. From experience and due to the widely varying resolutions employed on available systems, I personally do not rely on any granularity higher than second intervals.

10.19 A thread crashed that owned a read-write lock, how do I unlock it?

While a read-write lock acts like a mutex in many ways there is a bit of a difference in behavior when a thread crashes that "holds" the lock. That difference is that under some conditions threads may still be able to gain ownership of the rwlock.

The five basic behaviors you can typically expect are:

* If a writer crashes while holding the thread it is permanently locked and no other threads will be able to obtain it.

* If a reader crashes then usually, as long as only readers attempt to obtain the lock afterward, they will probably succeed.

* If a reader crashes and a writer attempts to gain the lock, then writers will always permanently permanently.

* If the rwlock gives preference to readers and a reader crashes while holding the lock then all subsequent writers will always block but it is implementation specific if future readers will be given access.

* If the rwlock gives preference to writers and a reader crashes while holding the lock then once a writer attempts to gain the lock then that writer and all subsequent readers and writers will block.

The descriptions I have given are a bit "loose" but do provide a general idea of what you can expect. Again, a lot of this behavior is implementation specific.

It should be mentioned that since the possibility exists for a reader to crash but future readers to still gain access to the critical section, a rwlock can be a lot more dangerous to use than a standard mutex.

There is a rational behind that previous statement. Even thought the crashed thread may have been a reader, which means it promised not to change data in the critical section, the effect of an uncontrolled crash may cause just such a modification. The end result is that your program could potentially continue execution under these conditions even though the data is in an indeterminant state.

10.20 Why are my reader threads suffering from starvation?

As was mentioned in the section introduction, most read-write lock implementations give preference to writers over readers. This means that if writers are trying to gain the lock at such a high frequency that there is always at least one writer is blocked and waiting for ownership then any incoming readers will never even have the chance to gain ownership. The end result is, of course, slow starvation of the readers.

Of course, you system could also for some reason give preference to readers instead. This situation is unlikely but not impossible. If it is the case and your reader threads are bombarding the rwlock then the writers may be starved.

If you run into either of these situations then the only real answer is that the rwlock is not appropriate and you should probably modify your code to use a regular mutex.

10.21 When should I use / not use a read-write lock?

The general things I look for when determining if a read-write lock would be more appropriate than a standard mutex are:

* the rwlock implementation gives preference to writers ( normal ) and twenty-five percent or less of the expected operations require write locks.

* the rwlock implementation gives preference to readers ( unusual ) and fifty percent or less of the expected operations require reader locks.

* clusters of either same type requests ( read or write ) occur together with a large interval of time passing between the clusters.

The last item may seem unusual but the premise is that if a burst of all writer operations occur then the rwlock will act like a regular mutex. If a burst of readers come thru then you will get the benefit of all the threads being allowed into the critical section concurrently. The reason for requiring the time interval is because if there wasn't adequate spacing between reader and writer bursts you could end up with thread starvation as discussed in a previous section.


©2003-2006 Michael M. Lampkin

All rights reserved.

Use of this website signifies your agreement to the Terms of Use.