Using read/write locks

In many situations, data is read more often than it is modified or written.

In these cases, you can allow threads to read concurrently while holding the lock and allow only one thread to hold the lock when data is modified. A multiple-reader single-writer lock (or read/write lock) does this. A read/write lock is acquired either for reading or writing, and then is released. The thread that acquires the read-write lock must be the one that releases it.

Read/write attributes object

The pthread_rwlockattr_init subroutine initializes a read-write lock attributes object (attr). The default value for all attributes is defined by the implementation. Unexpected results can occur if the pthread_rwlockattr_init subroutine specifies an already-initialized read/write lock attributes object.

The following examples illustrate how to call the pthread_rwlockattr_init subroutine with the attr object:
pthread_rwlockattr_t    attr;
and:
pthread_rwlockattr_init(&attr);

After a read/write lock attributes object is used to initialize one or more read/write locks, any function that affects the attributes object (including destruction) does not affect any previously initialized read/write locks.

The pthread_rwlockattr_destroy subroutine destroys a read/write lock attributes object. Unexpected results can occur if the object is used before it is reinitialized by another call to the pthread_rwlockattr_init subroutine. An implementation can cause the pthread_rwlockattr_destroy subroutine to set the object referenced by the attr object to an invalid value.

Creating and Destroying Read/Write Locks

The pthread_rwlock_init subroutine initializes the read/write lock referenced by the rwlock object with the attributes referenced by the attr object. If the attr object is NULL, the default read/write lock attributes are used; the effect is the same as passing the address of a default read-write lock attributes object. Upon successful initialization, the state of the read/write lock becomes initialized and unlocked. After initialized, the lock can be used any number of times without being reinitialized. Unexpected results can occur if the call to the pthread_rwlock_init subroutine is called specifying an already initialized read/write lock, or if a read/write lock is used without first being initialized.

If the pthread_rwlock_init subroutine fails, the rwlock object is not initialized and the contents are undefined.

The pthread_rwlock_destroy subroutine destroys the read/write lock object referenced by the rwlock object and releases any resources used by the lock. Unexpected results can occur in any of the following situations:
  • If the lock is used before it is reinitialized by another call to the pthread_rwlock_init subroutine.
  • An implementation can cause the pthread_rwlock_destroy subroutine to set the object referenced by the rwlock object to an invalid value. Unexpected results can occur if pthread_rwlock_destroy is called when any thread holds the rwlock object.
  • Attempting to destroy an uninitialized read/write lock results in unexpected results. A destroyed read/write lock object can be reinitialized by using the pthread_rwlock_init subroutine. Unexpected results can occur if the read/write lock object is referenced after it has been destroyed.
In cases where default read/write lock attributes are appropriate, use the PTHREAD_RWLOCK_INITIALIZER macro to initialize read/write locks that are statically allocated. For example:
pthread_rwlock_t        rwlock1 = PTHREAD_RWLOCK_INITIALIZER;
The effect is similar to dynamic initialization by using a call to the pthread_rwlock_init subroutine with the attr parameter specified as NULL, except that no error checks are performed. For example:
pthread_rwlock_init(&rwlock2, NULL);
The following example illustrates how to use the pthread_rwlock_init subroutine with the attr parameter initialized. For an example of how to initialize the attr parameter, see Read-Write Attributes Object.
pthread_rwlock_init(&rwlock, &attr);

Locking a read/write lock object for reading

The pthread_rwlock_rdlock subroutine applies a read lock to the read/write lock referenced by the rwlock object. The calling thread acquires the read lock if a writer does not hold the lock and if no writers are blocked on the lock. It is unspecified whether the calling thread acquires the lock when a writer does not hold the lock and there are writers waiting for the lock. If a writer holds the lock, the calling thread will not acquire the read lock. If the read lock is not acquired, the calling thread does not return from the pthread_rwlock_rdlock call until it can acquire the lock. Results are undefined if the calling thread holds a write lock on the rwlock object at the time the call is made.

A thread can hold multiple concurrent read locks on the rwlock object (that is, successfully call the pthread_rwlock_rdlock subroutine n times). If so, the thread must perform matching unlocks (that is, it must call the pthread_rwlock_unlock subroutine n times).

The pthread_rwlock_tryrdlock subroutine applies a read lock similar to the pthread_rwlock_rdlock subroutine with the exception that the subroutine fails if any thread holds a write lock on the rwlock object or if writers are blocked on the rwlock object. Results are undefined if any of these functions are called with an uninitialized read/write lock.

If a signal is delivered to a thread waiting for a read/write lock for reading, upon return from the signal handler, the thread resumes waiting for the read/write lock for reading as if it was not interrupted.

Locking a read/write lock object for writing

The pthread_rwlock_wrlock subroutine applies a write lock to the read/write lock referenced by the rwlock object. The calling thread acquires the write lock if no other thread (reader or writer) holds the read/write lock on the rwlock object. Otherwise, the thread does not return from the pthread_rwlock_wrlock call until it can acquire the lock. Results are undefined if the calling thread holds the read/write lock (whether a read or write lock) at the time the call is made.

The pthread_rwlock_trywrlock subroutine applies a write lock similar to the pthread_rwlock_wrlock subroutine, with the exception that the function fails if any thread currently holds rwlock for reading or writing. Results are undefined if any of these functions are called with an uninitialized read/write lock.

If a signal is delivered to a thread waiting for a read/write lock for writing, upon return from the signal handler, the thread resumes waiting for the read/write lock for writing as if it was not interrupted.

Preferring a writer-thread over a reader-thread

The pthread_rwlock_attr_setfavorwriters_np subroutine can be used by an application to initialize the attributes of a read/write lock. You can specify the pthread library to prioritize the scheduling of the threads that requires the read/write lock in write mode. When the pthread library schedules the writer-threads (threads that write data) to get the read/write lock in write mode, the pthread library does not support recursion by threads that are holding a the read/write lock in read mode. Unexpected results can occur when the threads hold a read/write lock in the read mode more than once.

The pthread_rwlock_attr_getfavorwriters_np subroutine returns the current preference that is set in the read/write lock attribute structure. By default, the reader-threads are prioritized over writer-threads to get a read/write lock.

Sample read/write lock programs

The following sample programs demonstrate how to use locking subroutines. To run these programs, you need the check.h file and makefile.

check.h file:
#include stdio.h
#include stdio.h
#include stdio.h
#include stdio.h

/* Simple function to check the return code and exit the program
   if the function call failed
   */
static void compResults(char *string, int rc) {
  if (rc) {
    printf("Error on : %s, rc=%d",
           string, rc);
    exit(EXIT_FAILURE);
  }
  return;
}
Make file:
CC_R = xlc_r

TARGETS = test01 test02 test03

OBJS = test01.o test02.o test03.o

SRCS = $(OBJS:.o=.c)

$(TARGETS): $(OBJS)
    $(CC_R) -o $@ $@.o

clean:
    rm $(OBJS) $(TARGETS)

run:
    test01
    test02
    test03

Single-thread example

The following example uses the pthread_rwlock_tryrdlock subroutine with a single thread. For an example of using the pthread_rwlock_tryrdlock subroutine with multiple threads, see Multiple-Thread Example.

Example: test01.c

#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"

pthread_rwlock_t       rwlock = PTHREAD_RWLOCK_INITIALIZER;

void *rdlockThread(void *arg)
{
  int             rc;
  int             count=0;

  printf("Entered thread, getting read lock with mp wait\n");
  Retry:
  rc = pthread_rwlock_tryrdlock(&rwlock);
  if (rc == EBUSY) {
    if (count >= 10) {
      printf("Retried too many times, failure!\n");

      exit(EXIT_FAILURE);
    }
    ++count;
    printf("Could not get lock, do other work, then RETRY...\n");
    sleep(1);
    goto Retry;
  }
  compResults("pthread_rwlock_tryrdlock() 1\n", rc);

  sleep(2);

  printf("unlock the read lock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Secondary thread complete\n");
  return NULL;
}

int main(int argc, char **argv)
{
  int                   rc=0;
  pthread_t             thread;

  printf("Enter test case - %s\n", argv[0]);

  printf("Main, get the write lock\n");
  rc = pthread_rwlock_wrlock(&rwlock);
  compResults("pthread_rwlock_wrlock()\n", rc);

  printf("Main, create the try read lock thread\n");
  rc = pthread_create(&thread, NULL, rdlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Main, wait a bit holding the write lock\n");
  sleep(5);

  printf("Main, Now unlock the write lock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Main, wait for the thread to end\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);
  printf("Main completed\n");
  return 0;
}

The output for this sample program will be similar to the following:

Enter test case - ./test01
Main, get the write lock
Main, create the try read lock thread
Main, wait a bit holding the write lock

Entered thread, getting read lock with mp wait
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Main, Now unlock the write lock
Main, wait for the thread to end
unlock the read lock
Secondary thread complete
Main completed

Multiple-thread example

The following example uses the pthread_rwlock_tryrdlock subroutine with multiple threads. For an example of using the pthread_rwlock_tryrdlock subroutine with a single thread, see Single-Thread Example.

Example: test02.c

#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"

pthread_rwlock_t       rwlock = PTHREAD_RWLOCK_INITIALIZER;

void *wrlockThread(void *arg)
{
  int             rc;
  int             count=0;

  printf("%.8x: Entered thread, getting write lock\n",
         pthread_self());
  Retry:
  rc = pthread_rwlock_trywrlock(&rwlock);
  if (rc == EBUSY) {
    if (count >= 10) {
      printf("%.8x: Retried too many times, failure!\n",
             pthread_self());
      exit(EXIT_FAILURE);
    }

    ++count;
    printf("%.8x: Go off an do other work, then RETRY...\n",
           pthread_self());
    sleep(1);
    goto Retry;
  }
  compResults("pthread_rwlock_trywrlock() 1\n", rc);
  printf("%.8x: Got the write lock\n", pthread_self());

  sleep(2);

  printf("%.8x: Unlock the write lock\n",
         pthread_self());
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("%.8x: Secondary thread complete\n",
         pthread_self());
  return NULL;
}

int main(int argc, char **argv)
{
  int                   rc=0;
  pthread_t             thread, thread2;

  printf("Enter test case - %s\n", argv[0]);

  printf("Main, get the write lock\n");
  rc = pthread_rwlock_wrlock(&rwlock);
  compResults("pthread_rwlock_wrlock()\n", rc);

  printf("Main, create the timed write lock threads\n");
  rc = pthread_create(&thread, NULL, wrlockThread, NULL);
  compResults("pthread_create\n", rc);

  rc = pthread_create(&thread2, NULL, wrlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Main, wait a bit holding this write lock\n");
  sleep(1);

  printf("Main, Now unlock the write lock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Main, wait for the threads to end\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_join(thread2, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);
  printf("Main completed\n");
  return 0;
}

The output for this sample program will be similar to the following:

Enter test case - ./test02
Main, get the write lock
Main, create the timed write lock threads
Main, wait a bit holding this write lock
00000102: Entered thread, getting write lock 
00000102: Go off an do other work, then RETRY...
00000203: Entered thread, getting write lock
00000203: Go off an do other work, then RETRY...
Main, Now unlock the write lock
Main, wait for the threads to end
00000102: Got the write lock
00000203: Go off an do other work, then RETRY...
00000203: Go off an do other work, then RETRY...
00000102: Unlock the write lock
00000102: Secondary thread complete
00000203: Got the write lock
00000203: Unlock the write lock
00000203: Secondary thread complete
Main completed

Read/write read-lock example

The following example uses the pthread_rwlock_rdlock subroutine to implement read/write read locks:

Example: test03.c

#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"

pthread_rwlock_t       rwlock;

void *rdlockThread(void *arg)
{
  int rc;

  printf("Entered thread, getting read lock\n");
  rc = pthread_rwlock_rdlock(&rwlock);
  compResults("pthread_rwlock_rdlock()\n", rc);
  printf("got the rwlock read lock\n");

  sleep(5);

  printf("unlock the read lock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);
  printf("Secondary thread unlocked\n");
  return NULL;
}

void *wrlockThread(void *arg)
{
  int rc;

  printf("Entered thread, getting write lock\n");
  rc = pthread_rwlock_wrlock(&rwlock);
  compResults("pthread_rwlock_wrlock()\n", rc);

  printf("Got the rwlock write lock, now unlock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);
  printf("Secondary thread unlocked\n");
  return NULL;
}



int main(int argc, char **argv)
{
  int                   rc=0;
  pthread_t             thread, thread1;

  printf("Enter test case - %s\n", argv[0]);

  printf("Main, initialize the read write lock\n");
  rc = pthread_rwlock_init(&rwlock, NULL);
  compResults("pthread_rwlock_init()\n", rc);

  printf("Main, grab a read lock\n");
  rc = pthread_rwlock_rdlock(&rwlock);
  compResults("pthread_rwlock_rdlock()\n",rc);

  printf("Main, grab the same read lock again\n");
  rc = pthread_rwlock_rdlock(&rwlock);
  compResults("pthread_rwlock_rdlock() second\n", rc);

  printf("Main, create the read lock thread\n");
  rc = pthread_create(&thread, NULL, rdlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Main - unlock the first read lock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Main, create the write lock thread\n");
  rc = pthread_create(&thread1, NULL, wrlockThread, NULL);
  compResults("pthread_create\n", rc);

  sleep(5);
  printf("Main - unlock the second read lock\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Main, wait for the threads\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_join(thread1, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);

  printf("Main completed\n");
  return 0;
}

The output for this sample program will be similar to the following:

$ ./test03
Enter test case - ./test03
Main, initialize the read write lock
Main, grab a read lock
Main, grab the same read lock again
Main, create the read lock thread
Main - unlock the first read lock
Main, create the write lock thread
Entered thread, getting read lock
got the rwlock read lock
Entered thread, getting write lock
Main - unlock the second read lock
Main, wait for the threads
unlock the read lock
Secondary thread unlocked
Got the rwlock write lock, now unlock
Secondary thread unlocked
Main completed