読み取り/書き込みロックの使用

多くの場合、 データの読み取りは、 変更または書き込みよりも頻繁に行われます。

このような場合、 複数のスレッドがロックを保持しながら並行して読み取りを行えるようにし、 データの変更時には、1 つのスレッドだけがロックを保持できるようにすることができます。 この処理は、マルチリーダー・シングルライター・ロック (または読み取り/書き込みロック) で行えます。 読み取り/書き込みロックは、読み取り用か書き込み用のいずれかに獲得され、その後、解除されます。 読み取り/書き込みロックを獲得したスレッドによって、そのロックの解除が行われなければなりません。

読み取り/書き込み属性オブジェクト

pthread_rwlockattr_init サブルーチンは、 読み取り/書き込みロック属性オブジェクト (attr) を初期化します。 このインプリメンテーションでは、すべての属性のデフォルト値が定義されます。 pthread_rwlockattr_init サブルーチンが、既に初期化されている読み取り/書き込みロック属性オブジェクトを指定する場合、想定外の結果が生じる可能性があります。

以下の例では、 attr オブジェクトで pthread_rwlockattr_init サブルーチンを呼び出す方法について説明します。
pthread_rwlockattr_t    attr;
および
pthread_rwlockattr_init(&attr);

読み取り/書き込みロック属性オブジェクトが使用されて、1 つ以上の読み取り/書き込みロックが初期化されると、属性オブジェクトに影響するどのような関数 (破棄も含む) も、既に初期化されている読み取り/書き込みロックに影響を与えることはありません。

pthread_rwlockattr_destroy サブルーチンは、読み取り/書き込みロック属性オブジェクトを破棄します。 pthread_rwlockattr_init サブルーチンへの別の呼び出しによって再初期化される前に、 このオブジェクトが使用されると、 想定外の結果が生じます。 インプリメンテーションによっては、 pthread_rwlockattr_destroy サブルーチンは、 attr オブジェクトが参照するオブジェクトを無効な値に設定する可能性があります。

読み取り/書き込みロックの作成と破棄

pthread_rwlock_init サブルーチンは、attr オブジェクトによって参照される属性によって、rwlock オブジェクトによって参照される読み取り/書き込みロックを初期化します。 attr オブジェクトが NULL の場合、デフォルトの読み取り/書き込みロック属性が使用されます。これは、デフォルトの読み取り/書き込みロック属性オブジェクトのアドレスを渡すのと同じ結果になります。 初期化が正常に行われると、読み取り/書き込みロックは、初期化されてアンロックされた状態になります。 いったん初期化されたロックは、再初期化を行わなくても、何回でも使用することができます。 既に初期化されている読み取り/書き込みロックを指定して pthread_rwlock_init サブルーチンを呼び出した場合、またはまず初期化を行わずに読み取り/書き込みロックを使用した場合、想定外の結果が生じる可能性があります。

pthread_rwlock_init サブルーチンが失敗した場合、 rwlock オブジェクトは初期化されず、 内容も定義されません。

pthread_rwlock_destroy サブルーチンは、rwlock オブジェクトによって参照される読み取り/書き込みロック・オブジェクトを破棄し、ロックによって使用されていたすべてのリソースを解放します。 以下のいずれかの状態では、 想定外の結果が生じます。
  • pthread_rwlock_init サブルーチンへの別の呼び出しによって再初期化される前に、 ロックが使用された場合。
  • インプリメンテーションによっては、pthread_rwlock_destroy サブルーチンは、rwlock オブジェクトが参照するオブジェクトを無効な値に設定する可能性があります。 何らかのスレッドが rwlock オブジェクトを保持しているときに、pthread_rwlock_destroy が呼び出されると、 想定外の結果が生じます。
  • 初期化されていない読み取り/書き込みロックを破棄しようとすると、想定外の結果が生じます。 破棄された読み取り/書き込みロック・オブジェクトは、pthread_rwlock_init サブルーチンを使用して再初期化することができます。 読み取り/書き込みロック・オブジェクトが破棄された後で参照されると、想定外の結果が生じる可能性があります。
デフォルトの読み取り/書き込みロック属性が適切である場合、PTHREAD_RWLOCK_INITIALIZER マクロを使用して、静的に割り当てられる読み取り/書き込みロックを初期化してください。 例:
pthread_rwlock_t        rwlock1 = PTHREAD_RWLOCK_INITIALIZER;
これは、attr パラメーターを NULL として指定し pthread_rwlock_init サブルーチンへの呼び出しを使用して、動的に初期化を行ったのと同じ結果になります。ただし、エラー検査は実行されません。 例:
pthread_rwlock_init(&rwlock2, NULL);
以下の例では、attr パラメーターを初期化して pthread_rwlock_init サブルーチンを使用する方法を示しています。 attr パラメーターを初期化する方法の例については、読み取り/書き込み属性オブジェクトを参照してください。
pthread_rwlock_init(&rwlock, &attr);

読み取りのための読み取り/書き込みロック・オブジェクトのロック

pthread_rwlock_rdlock サブルーチンは、rwlock オブジェクトによって参照される読み取り/書き込みロックに読み取りロックを適用します。 ライターがそのロックを保持していない場合、またそのロックでブロックされているライターがない場合、コール側のスレッドは読み取りロックを獲得します。 ライターがそのロックを保持しておらず、 そのロックを待っているライターがない場合、 コール側のスレッドが読み取りロックを獲得するかどうかは未指定です。 ライターがそのロックを保持している場合、 コール側のスレッドは読み取りロックを獲得しません。 読み取りロックが獲得されなかった場合、 ロックを獲得できるまで、 コール側のスレッドは pthread_rwlock_rdlock 呼び出しから戻りません。 呼び出しが行われた時点で、 コール側のスレッドが rwlock オブジェクトに対する書き込みロックを保持している場合、 結果は未定義です。

スレッドは、rwlock オブジェクトに対する複数の読み取りロックを並行して保持できます (つまり、pthread_rwlock_rdlock サブルーチンを n 回、正常に呼び出せます)。 その場合、 そのスレッドは対応する数のアンロックを実行しなければなりません (つまり、 pthread_rwlock_unlock サブルーチンを n 回呼び出さなければなりません)。

pthread_rwlock_tryrdlock サブルーチンは、pthread_rwlock_rdlock サブルーチンと同じように読み取りロックを適用します。ただし、何らかのスレッドが rwlock オブジェクトに対する書き込みロックを保持している場合、または rwlock オブジェクトでブロックされているライターがある場合、このサブルーチンは失敗します。 初期化されていない読み取り/書き込みロックに対してこれらの関数のいずれかが呼び出された場合、結果は未定義です。

読み取りのために読み取り/書き込みロックを待っているスレッドにシグナルが送達されると、シグナル・ハンドラーから戻った時点で、スレッドは中断がなかったかのように、読み取りのための読み取り/書き込みロックの待機を再開します。

書き込みのための読み取り/書き込みロック・オブジェクトのロック

pthread_rwlock_wrlock サブルーチンは、rwlock オブジェクトによって参照される読み取り/書き込みロックに書き込みロックを適用します。 読み取り/書き込みロックを rwlock オブジェクトで保持している他のスレッド (リーダーまたはライター) がなければ、コール側のスレッドは書き込みロックを獲得します。 そうでない場合、 ロックを獲得できるまで、 そのスレッドは pthread_rwlock_wrlock 呼び出しから戻りません。 呼び出しが行われた時点で、コール側のスレッドが読み取り/書き込みロック (読み取りロックか書き込みロックのいずれか) を保持している場合、結果は未定義です。

pthread_rwlock_trywrlock サブルーチンは、pthread_rwlock_wrlock サブルーチンと同じように書き込みロックを適用します。 ただし、何らかのスレッドが読み取りまたは書き込みのために、rwlock を現在保持している場合、 この関数は失敗します。 初期化されていない読み取り/書き込みロックに対してこれらの関数のいずれかが呼び出された場合、結果は未定義です。

書き込みのために読み取り/書き込みロックを待っているスレッドにシグナルが送達されると、シグナル・ハンドラーから戻った時点で、スレッドは中断がなかったかのように、読み取りのための読み取り/書き込みロックの待機を再開します。

変更の開始

ライター・スレッドがリーダー・スレッドより優先される

pthread_rwlock_attr_setfavorwriters_np サブルーチンは、読み取り/書き込みロックの属性を初期化するためにアプリケーションが使用できます。 pthread ライブラリーを指定して、書き込みモードの読み取り/書き込みロックを必要とするスレッドのスケジューリングに優先順位を付けることができます。 pthread ライブラリーが、読み取り/書き込みロックを読み取りモードで取得するためにライター・スレッド (データを書き込むスレッド) をスケジューリングするとき、その pthread ライブラリーでは、読み取り/書き込みロックを読み取りモードで保持しているスレッドによるリカージョンはサポートされません。 スレッドが複数回、読み取り/書き込みロックを読み取りモードで保持すると、予期しない結果が生じる可能性があります。

pthread_rwlock_attr_getfavorwriters_np サブルーチンは、読み取り/書き込みロック属性構造に設定されている現行設定を返します。 デフォルトで、読み取り/書き込みロックを取得するためには、リーダー・スレッドがライター・スレッドより優先されます。

変更の終了

読み取り/書き込みロック・プログラムの例

以下のサンプル・プログラムでは、 サブルーチンのロックを使用する方法について示しています。 これらのプログラムを実行するには、check.h ファイルおよび makefile が必要です。

check.h ファイル:
#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 ファイル:
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

単一スレッドの例

以下の例では、 単一スレッドで pthread_rwlock_tryrdlock サブルーチンが使用されます。 複数のスレッドで pthread_rwlock_tryrdlock サブルーチンを使用する例については、マルチスレッドの例を参照してください。

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;
}

このサンプル・プログラムの出力は以下のようになります。

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

マルチスレッドの例

以下の例では、 マルチスレッドで pthread_rwlock_tryrdlock サブルーチンが使用されます。 単一スレッドで pthread_rwlock_tryrdlock サブルーチンを使用する例については、単一スレッドの例を参照してください。

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;
}

このサンプル・プログラムの出力は以下のようになります。

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

読み取り/書き込みの読み取りロックの例

次の例では、pthread_rwlock_rdlock サブルーチンを使用して、読み取り/書き込みの読み取りロックをインプリメントします。

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;
}

このサンプル・プログラムの出力は以下のようになります。

$ ./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