マルチスレッドのプログラミング

相互排他ロックの使用方法

表 4–3 に、相互排他ロックを操作する関数を示します。

表 4–3 相互排他ロック操作ルーチン

操作 

参照先 

mutex の初期化 

pthread_mutex_init の構文」

mutex の整合性保持 

pthread_mutex_consistent_np の構文」

mutex のロック 

pthread_mutex_lock の構文」

mutex のロック解除 

pthread_mutex_unlock の構文」

ブロックしないで行う mutex のロック 

pthread_mutex_trylock の構文」

指定した時間までの mutex のロック 

pthread_mutex_timedlock() の構文」

指定した時間間隔内の mutex のロック 

pthread_mutex_reltimedlock_np() の構文」

mutex の削除 

pthread_mutex_destroy の構文」

デフォルトスケジューリングポリシー SCHED_OTHER は、スレッドによるロックの獲得順序を指定していません。複数の SCHED_OTHER スレッドが mutex を待っているときの獲得の順序は不定です。SCHED_FIFO および SCHED_RR リアルタイムスケジューリングポリシーの下では、優先順位に従って待機スレッドをブロック解除します。

mutex の初期化

mp が指す mutex をデフォルト値に初期化する場合、またはすでに pthread_mutexattr_init() で設定されている mutex 属性を指定する場合は、pthread_mutex_init(3C) を使用します。mattr のデフォルト値は NULL です。

pthread_mutex_init の構文

int pthread_mutex_init(pthread_mutex_t *restrict mp,
          const pthread_mutexattr_t *restrict mattr);
#include <pthread.h>

pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t mattr;
int ret;

/* initialize a mutex to its default value */
ret = pthread_mutex_init(&mp, NULL);

/* initialize a mutex */
ret = pthread_mutex_init(&mp, &mattr); 

初期化された mutex は、ロック解除状態になります。mutex は、プロセス間で共有されているメモリー内または個々のプロセス専用のメモリー内に置かれます。


注 –

PTHREAD_MUTEX_ROBUST_NP 属性で初期化される mutex の場合は、初期化の前に mutex メモリーをゼロにクリアーする必要があります。


mattrNULL にするのは、デフォルト mutex 属性オブジェクトのアドレスを渡すのと同じことですが、メモリーのオーバーヘッドがありません。

静的に定義された mutex をデフォルト値に初期化するには、マクロ PTHREAD_MUTEX_INITIALIZER を使用します。

ほかのスレッドによって使用されている mutex は、初期化したり削除したりできません。初期化や削除の動作が正常に行われないと、プログラムで障害が発生します。mutex を再初期化または削除する場合、アプリケーションがその mutex を使用していないことが確実でなければなりません。

pthread_mutex_init の戻り値

pthread_mutex_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EBUSY

説明:

mp で示されたオブジェクト (初期化されているが、まだ削除されていない mutex) の再初期化の試行が検出されました。


EINVAL

説明:

mattr 属性値が無効です。その mutex は変更されていません。


EFAULT

説明:

mp が指す mutex のアドレスが無効です。

mutex の整合性保持

堅牢な mutex の所有者がその mutex をロック解除しないで終了すると、その mutex はロック解除され、不整合としてマークされます。次の所有者が EOWNERDEAD のリターンコードを持つロックを獲得します。

pthread_mutex_consistent_np() は、その所有者が処理を終了したあとも mutex オブジェクト mutex の整合性を保持します。

pthread_mutex_consistent_np の構文

#include <pthread.h> 
int pthread_mutex_consistent_np(pthread_mutex_t *mutex); 

不整合の mutex を獲得するには、pthread_mutex_lock() を呼び出します。戻り値 EOWNERDEAD は不整合な mutex であることを示します。

pthread_mutex_consistent_np() は、pthread_mutex_lock() への前の呼び出しによって獲得された mutex を保持している間に呼び出してください。

mutex によって保護されている重要領域が、所有者の処理の失敗のため不整合の状態のままになっていることがあります。この場合は、この領域を整合させることができる場合にのみ mutex を整合させてください。

整合された mutex に対して pthread_mutex_lock()pthread_mutex_unlock() および pthread_mutex_trylock() を呼び出すと、通常の方法で動作します。

不整合でない、あるいは保持されていない mutex に対する pthread_mutex_consistent_np() の動作は、定義されていません。

pthread_mutex_consistent_np の戻り値

pthread_mutex_consistent_np () は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次の条件が検出されると、pthread_mutex_consistent_np() は失敗します。


EINVAL

説明:

現在のスレッドが mutex を所有していないか、または mutex が不整合の状態を持つ PTHREAD_MUTEX_ROBUST_NP mutex ではありません。

mutex のロック

mutex が指す mutex をロックするには、pthread_mutex_lock(3C) を使用します。

pthread_mutex_lock の構文

int  pthread_mutex_lock(pthread_mutex_t *mutex); 
#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_ mutex_lock(&mp); /* acquire the mutex */

mutex は、pthread_mutex_lock() から制御が戻るとロックされます。呼び出しスレッドが所有者になります。mutex が別のスレッドによってすでにロックされている (所有されている) 場合は、呼び出しスレッドは mutex が使用可能になるまでブロックされます。

mutex 型が PTHREAD_MUTEX_NORMAL の場合、デッドロックの検出は行われません。mutex をもう一度ロックしようとするとデッドロックが発生します。スレッドが、スレッドによってロックされていない mutex やロック解除された mutex をロック解除しようとした場合、引き起こされる動作は未定義です。

mutex 型が PTHREAD_MUTEX_ERRORCHECK の場合、エラーチェックが行われます。スレッドが、すでにロックした mutex をもう一度ロックしようとすると、エラーが返されます。スレッドが、スレッドによってロックされていない mutex やロック解除された mutex をロック解除しようとすると、エラーが返されます。

mutex 型が PTHREAD_MUTEX_RECURSIVE の場合、mutex はロックカウントの概念を持ちます。スレッドが初めて正常に mutex を獲得すると、ロックカウントは 1 に設定されます。この mutex をスレッドがさらにロックするたびに、ロックカウントが 1 ずつ増えます。スレッドが mutex をロック解除するたびに、ロックカウントが 1 ずつ減ります。ロックカウントが 0 になると、別のスレッドがこの mutex を獲得できるようになります。スレッドが、スレッドによってロックされていない mutex やロック解除された mutex をロック解除しようとすると、エラーが返されます。

mutex 型 PTHREAD_MUTEX_DEFAULTPTHREAD_MUTEX_NORMAL と同じです。

pthread_mutex_lock の戻り値

pthread_mutex_lock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EAGAIN

説明:

mutex の再帰的なロックが最大数を超えるため、mutex を獲得できません。


EDEADLK

説明:

現在のスレッドがすでにその mutex を獲得しています。

mutex が PTHREAD_MUTEX_ROBUST_NP の堅牢度属性で初期化された場合、pthread_mutex_lock() は次のいずれかの値を返す可能性があります。


EOWNERDEAD

説明:

この mutex の前の所有者が mutex を保持している間に終了しました。現在この mutex は、呼び出し元によって所有されています。呼び出し元は、mutex によって保護された状態を整合させるよう試行する必要があります。

呼び出し元が状態を整合させることができた場合、その mutex に対して pthread_mutex_consistent_np() を呼び出して、mutex をロック解除します。これ以降の pthread_mutex_lock() の呼び出しは正常に動作します。

呼び出し元が状態を整合させることができない場合は、その mutex に対して pthread_mutex_init() を呼び出さないでください。代わりに mutex をロック解除します。これ以降の pthread_mutex_lock() のすべての呼び出しは mutex の獲得に失敗し、エラーコード ENOTRECOVERABLE を返します。

EOWNERDEAD を持つロックを獲得した所有者が mutex を保持している間に終了すると、次の所有者が EOWNERDEAD を持つロックを獲得します。


ENOTRECOVERABLE

説明:

獲得しようとしている mutex は、この mutex の前の所有者によって回復不能のままにされた状態を保護していました。mutex は獲得されませんでした。この回復可能な状態は、次のような場合に発生します。

  • 以前、このロックが EOWNERDEAD によって取得された

  • 所有者が状態をクリーンアップできなかった

  • 所有者が mutex の状態を整合させないで mutex をロック解除した


ENOMEM

説明:

同時に保持される mutex の上限数を超えています。

mutex のロック解除

mutex が指す mutex をロック解除するには、pthread_mutex_unlock(3C) を使用します。

pthread_mutex_unlock の構文

int pthread_mutex_unlock(pthread_mutex_t *mutex); 
#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_mutex_unlock(&mutex); /* release the mutex */

pthread_mutex_unlock() は、mutex が指す mutex オブジェクトを解放します。mutex を解放する方法は、mutex の 型属性に依存します。pthread_mutex_unlock() が呼び出されたときに、mutex オブジェクトでブロックされているスレッドがある場合、mutex が使用可能な状態になると、スケジューリングポリシーに基づいて mutex を獲得するスレッドが決定されます。PTHREAD_MUTEX_RECURSIVE のタイプの mutex の場合、mutex が使用可能になるのは、カウントが 0 になり、pthread_mutex_unlock を呼び出したスレッドがこの mutex のロックを解除したときです。

pthread_mutex_unlock の戻り値

pthread_mutex_unlock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EPERM

説明:

現在のスレッドは mutex を所有していません。

ブロックしないで行う mutex のロック

mutex が指す mutex のロックを試行し、その mutex がすでにロックされていればただちに戻るには、pthread_mutex_trylock(3C) を使用します。

pthread_mutex_trylock の構文

int pthread_mutex_trylock(pthread_mutex_t *mutex); 
#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */

この関数はブロックしない点を除いて、pthread_mutex_lock() と同じ働きをします。()mutex が参照している mutex オブジェクトが、現在のスレッドを含むいずれかのスレッドによってロックされている場合は、呼び出しはただちに返されます。mutex オブジェクトがロックされていなければ、呼び出しスレッドがロックを獲得します。

pthread_mutex_trylock の戻り値

pthread_mutex_trylock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EBUSY

説明:

mutex が指している mutex はすでにロックされているため、獲得できません。


EAGAIN

説明:

mutex に繰り返し行われたロック回数が最大数を超えるため、mutex を所有できません。

シンボル _POSIX_THREAD_PRIO_INHERIT が定義されていて、mutex がプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されています。さらに、pthread_mutexattr_setrobust_np() の引数 robustnessPTHREAD_MUTEX_ROBUST_NP の場合、この関数は失敗し、次のいずれかの値を返します。


EOWNERDEAD

説明:

pthread_mutex_lock の戻り値」の説明を参照してください。


ENOTRECOVERABLE

説明:

pthread_mutex_lock の戻り値」の説明を参照してください。


ENOMEM

説明:

同時に保持される mutex の上限数を超えています。

指定した絶対時間までの mutex のロック

指定した時間まで mutex オブジェクトのロックを試行するには、pthread_mutex_timedlock(3C) 関数を使用します。

この関数は無期限にブロックされることがない点を除いて、pthread_mutex_lock() 関数と同じ動作をします。その mutex がすでにロックされている場合は、mutex が使用可能になるまで呼び出しスレッドはブロックされますが、それもタイムアウトに達するまでの間だけです。mutex が使用可能になる前にタイムアウトが発生すると、関数は終了します。

pthread_mutex_timedlock() の構文

int  pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, 
          const struct timespec *restrict abs_timeout);
#include <pthread.h>
#include <time.h>

pthread_mutex_t mutex;
timestruct_t abs_timeout;
int ret;

ret = pthread_mutex_timedlock(&mutex,  &abs_timeout); 

pthread_mutex_timedlock() の戻り値

pthread_mutex_timedlock() 関数は、mutex を正常にロックすると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

説明:

mutex が PTHREAD_PRIO_PROTECT の値を持つプロトコル属性で作成されており、呼び出しスレッドの優先順位がこの mutex の現在の優先順位上限を超えています。

説明:

mutex で指定された値が、初期化された mutex オブジェクトを表していません。

説明:

プロセスまたはスレッドがブロックされており、abs_timeout パラメータで指定されたナノ秒のフィールド値が 0 未満または 10 億以上です。


ETIMEDOUT

説明:

指定されたタイムアウトの期限が切れる前に mutex をロックできませんでした。

pthread_mutex_lock の戻り値」の説明を参照してください。

指定した時間間隔内の mutex のロック

指定した時間が経過するまで mutex オブジェクトのロックを試行するには、pthread_mutex_reltimedlock_np(3C) 関数を使用します。

タイムアウトの期限が切れるのは、rel_timeout で指定された時間間隔 (CLOCK_REALTIME クロックで測定される) が経過した場合、または呼び出し時に rel_timeout で指定された時間間隔が負である場合です。

pthread_mutex_reltimedlock_np() の構文

int  pthread_mutex_reltimedlock_np(pthread_mutex_t *restrict mutex, 
          const struct timespec *restrict rel_timeout);
#include <pthread.h>
#include <time.h>

pthread_mutex_t mutex;
timestruct_t rel_timeout;
int ret;

ret = pthread_mutex_reltimedlock_np(&mutex,  &rel_timeout); 

pthread_mutex_reltimedlock_np() の戻り値

pthread_mutex_reltimedlock_np() 関数は、mutex を正常にロックすると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

説明:

mutex が PTHREAD_PRIO_PROTECT の値を持つプロトコル属性で作成されており、呼び出しスレッドの優先順位がこの mutex の現在の優先順位上限を超えています。

説明:

mutex で指定された値が、初期化された mutex オブジェクトを表していません。

説明:

プロセスまたはスレッドがブロックされており、abs_timeout パラメータで指定されたナノ秒のフィールド値が 0 未満または 10 億以上です。


ETIMEDOUT

説明:

指定されたタイムアウトの期限が切れる前に mutex をロックできませんでした。

pthread_mutex_lock の戻り値」の説明を参照してください。

mutex の削除

mp が指す mutex に関連付けられた状態を削除するには、pthread_mutex_destroy(3C) を使用します。

pthread_mutex_destroy の構文

int pthread_mutex_destroy(pthread_mutex_t *mp); 
#include <pthread.h>

pthread_mutex_t mp;
int ret;

ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */

mutex の記憶領域は解放されません。

pthread_mutex_destroy の戻り値

pthread_mutex_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。


EINVAL

説明:

mp で指定された値が、初期化された mutex オブジェクトを表していません。

mutex ロックのコード例

例 4–1 に、mutex ロックを使用したコードの抜粋を示します。


例 4–1 mutex ロックの例

#include <pthread.h>

pthread_mutex_t count_mutex;
long long count;

void
increment_count()
{
    pthread_mutex_lock(&count_mutex);
    count = count + 1;
    pthread_mutex_unlock(&count_mutex);
}

long long
get_count()
{
    long long c;
    
    pthread_mutex_lock(&count_mutex);
    c = count;
    pthread_mutex_unlock(&count_mutex);
    return (c);
}

例 4–1 では、2 つの関数がそれぞれ別の目的で相互排他ロックを使用しています。increment_count() 関数は、相互排他ロックによって共有変数の不可分操作による更新を保証しています。get_count() 関数は、相互排他ロックによって 64 ビット 値の count が不可分に読み取られるようにしています。32 ビットアーキテクチャーでは、long long は実際には 2 つの 32 ビット値として処理されます。

整数はほとんどのマシンで共通のワードサイズであるため、整数値の読み取りは不可分操作です。

ロック階層の使用例

同時に 2 つのリソースをアクセスすることがあります。一方のリソースを使用しているとき、もう一方のリソースも必要となる場合があります。2 つのスレッドが同じ 2 つのリソースを要求しようとして、対応する相互排他ロックを異なる順序で獲得しようとすると、問題が発生します。たとえば、2 つのスレッドがそれぞれ mutex の 1 と 2 をロックし、次に各スレッドが互いにもう一方の mutex をロックしようとすると、デッドロックが発生します。例 4–2 に、デッドロックが発生する場合のシナリオを示します。


例 4–2 デッドロック

スレッド 1 

スレッド 2 

pthread_mutex_lock(&m1);

/* use resource 1 */


pthread_mutex_lock(&m2);


/* use resources 1 and 2 */


pthread_mutex_unlock(&m2);


pthread_mutex_unlock(&m1);
pthread_mutex_lock(&m2);

/* use resource 2 */


pthread_mutex_lock(&m1);


/* use resources 1 and 2 */


pthread_mutex_unlock(&m1);


pthread_mutex_unlock(&m2);


この問題を回避する最善の方法は、スレッドで複数の mutex をロックする場合、常に同じ順序でロックすることです。ロックが常に規定された順序で実行されれば、デッドロックは起こらないはずです。この方法を「ロック階層」と呼びます。この方法では、mutex に論理的な番号を割り振って、mutex に順序を付けます。

自分が持つ mutex の番号より小さい番号が割り振られている mutex はロックできないという規定を守るようにします。

ただし、この方法が使用できない場合もあります。規定と違う順序で相互排他ロックを獲得しなければならないこともあるからです。そのような状況でデッドロックを防ぐには、pthread_mutex_trylock() を使用します。デッドロックが避けられないような事態が生じた場合は、あるスレッドが現在保持している mutex のロックを解除する必要があります。


例 4–3 条件付きロック

スレッド 1 

スレッド 2 

pthread_mutex_lock(&m1); pthread_mutex_lock(&m2);

 

 

 

 

/* 解放 */ 

 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

for (; ;)

{ pthread_mutex_lock(&m2);

 

 

if(pthread_mutex_trylock(&m1)==0)

/* 獲得成功 */ 

break;

/* 獲得失敗 */ 

pthread_mutex_unlock(&m2);

}

/* ロックを獲得し、解放 */ 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


例 4–3 では、スレッド 1 は mutex を規定通りの順序でロックしようとしていますが、スレッド 2 ではロックの順序が違います。デッドロックが発生しないようにするために、スレッド 2 は mutex 1 を慎重にロックしなければなりません。これは、スレッド 2 が mutex 1 が解放されるまで待つとすると、スレッド 1 との間にデッドロックの関係が生じる恐れがあるからです。

デッドロックの発生を防ぐため、スレッド 2 は pthread_mutex_trylock() を呼び出し、mutex が使用可能な状態であれば獲得します。mutex が使用可能な状態でない場合、スレッド 2 はただちに終了し、エラーを返します。その時点で、スレッド 2 は mutex 2 を解放しなければなりません。その結果、スレッド 1 は mutex 2 をロックでき、その後 mutex 1 と mutex 2 の両方を解放します。

片方向リンクリストの入れ子のロック

例 4–4例 4–5 では、一度に 3 つのロックを獲得する方法について説明します。デッドロックを防ぐため、規定された順序でロックします。


例 4–4 片方向リンクのリスト構造体

typedef struct node1 {
    int value;
    struct node1 *link;
    pthread_mutex_t lock;
} node1_t;

node1_t ListHead;

この例で使用する片方向リンクのリスト構造体は、各ノードに相互排他ロックを含んでいます。このリストから特定のノードを削除する場合は、最初に ListHead の位置からリストをたどって目的のノードを探します。ListHead が削除されることはありません。

この検索を同時並行的に行われる削除から保護するために、各ノードをロックしてからノードの内容にアクセスしなければなりません。すべての検索が ListHead の位置から開始されるので、常にリストの順序でロックされます。このため、デッドロックが発生することはありません。

目的のノードが見つかったら、この変更がそのノードと直前のノードの両方に影響を与えるため、両方をロックします。直前のノードのロックが常に最初に獲得されるので、ここでもデッドロックの心配はありません。例 4–5 は、片方向リンクリストから特定のノードを削除する C コードです。


例 4–5 片方向リンクリストの入れ子のロック

node1_t *delete(int value)
{
    node1_t *prev, *current;

    prev = &ListHead;
    pthread_mutex_lock(&prev->lock);
    while ((current = prev->link) != NULL) {
        pthread_mutex_lock(&current->lock);
        if (current->value == value) {
            prev->link = current->link;
            pthread_mutex_unlock(&current->lock);
            pthread_mutex_unlock(&prev->lock);
            current->link = NULL;
            return(current);
        }
        pthread_mutex_unlock(&prev->lock);
        prev = current;
    }
    pthread_mutex_unlock(&prev->lock);
    return(NULL);
}

循環リンクリストの入れ子のロック

例 4–6 は、前述のリスト構造を修正して循環リストにしたものです。先頭のノードとして識別されるノードはありません。スレッドは適当な 1 つのノードに関連付けられ、そのノードと近傍ノードに対して操作を行います。この状況ではロック序列は適用できません。明らかに階層 (つまり、リンクをたどる順番) が循環的だからです。


例 4–6 循環リンクリスト

typedef struct node2 {
    int value;
    struct node2 *link;
    pthread_mutex_t lock;
} node2_t;

以下は、2 つのノードをロックし、両方のノードに対してある操作を行う C コードです。


例 4–7 循環リンクリストの入れ子のロック

void Hit Neighbor(node2_t *me) {
    while (1) {
        pthread_mutex_lock(&me->lock);
        if (pthread_mutex_trylock(&me->link->lock)!= 0) {
            /* failed to get lock */             
            pthread_mutex_unlock(&me->lock);              
            continue;         
        }         
        break;     
    }     
    me->link->value += me->value;     
    me->value /=2;     
    pthread_mutex_unlock(&me->link->lock);     
    pthread_mutex_unlock(&me->lock);
}