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

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

表 4-3 に、この章で説明する mutex ロック操作関数を示します。

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

操作 

参照先 

mutex の初期化 

「pthread_mutex_init(3THR)」

mutex の整合性保持 

「pthread_mutex_consistent_np(3T)」

mutex のロック 

「pthread_mutex_lock(3THR)」

mutex のロック解除 

「pthread_mutex_unlock(3THR)」

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

「pthread_mutex_trylock(3THR)」

mutex の削除 

「pthread_mutex_destroy(3THR)」

デフォルトスケジューリング方針 SCHED_OTHER は、スレッドによるロックの獲得順序を指定していません。複数のスレッドが mutex を待っているときの獲得の順序は不定です。競合するときは、スレッドを優先順位でブロック解除するというのがデフォルト動作です。

mutex の初期化

pthread_mutex_init(3THR)

pthread_mutex_init(3THR) は、mp が指す mutex をデフォルト値に初期化 (mattr が NULL の場合) するか、pthread_mutexattr_init() ですでに設定されている mutex 属性を指定するときに使用します。(Solaris スレッドについては、「mutex_init(3THR)」を参照)。


プロトタイプ:
int	pthread_mutex_init(pthread_mutex_t *mp,
    const pthread_mutexattr_t *mattr);

#include <pthread.h>

pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t mattr;
int ret;

/* mutex をデフォルト値に初期化する */
ret = pthread_mutex_init(&mp, NULL);

/* mutex を初期化する */
ret = pthread_mutex_init(&mp, &mattr);

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


注 -

mutex メモリーは、初期化する前にクリアしてゼロにする必要があります。


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

mutex を静的に定義する場合、マクロ PTHREAD_MUTEX_INITIALIZER により、デフォルト属性を持つように直接初期化できます。

mutex ロックは、他のスレッドが使用している可能性がある間は再初期化したり削除したりしてはいけません。どちらの動作も正しく行われなければプログラムで障害が発生します。mutex を再初期化または削除する場合、アプリケーションがその mutex を使用していないことが確実でなければなりません。

戻り値

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


EBUSY

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


EINVAL

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


EFAULT

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

mutex の整合性保持

pthread_mutex_consistent_np(3T)


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

注 -

pthread_mutex_consistent_np() が適用されるのは、シンボル _POSIX_THREAD_PRIO_INHERIT が定義され、かつプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されている mutex に対してのみです。


mutex の所有者が終了すると、mutex が不整合になる可能性があります。

pthread_mutex_consistent_np が、mutex の所有者の終了後に mutex オブジェクト、mutex を整合させます。

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

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

mutex によって保護されている重要領域が、終了した所有者によって不整合の状態のままになっている可能性があるので、mutex によって保護されている重要領域を整合させることができる場合にのみ mutex を整合させてください。

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

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

戻り値

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

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


ENOSYS

オプション _POSIX_THREAD_PRIO_INHERIT が定義されていないか、あるいはこの実装が pthread_mutex_consistent_np() 関数をサポートしていません。

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


EINVAL

mutex で指定された値は無効です。

mutex のロック

pthread_mutex_lock(3THR)


プロトタイプ:
int	pthread_mutex_lock(pthread_mutex_t *mutex);

#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_ mutex_lock(&mp); /* mutex を獲得する */

pthread_mutex_lock(3THR) は、mutex が指す mutex をロックします。pthread_mutex_lock() が戻ると、呼び出しスレッドが mutex をロックした状態になっています。mutex が別のスレッドによってすでにロックされている (所有されている) 場合は、呼び出しスレッドは mutex が使用可能になるまでブロックされます (Solaris スレッドについては、「mutex_lock(3THR)」を参照)。

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_DEFAULT の場合、繰り返し mutex をロックしようとすると、引き起こされる動作は未定義です。mutex をロックしていないスレッドがロック解除しようとした場合、引き起こされる動作は未定義です。また、ロックされていない mutex をロック解除しようとした場合、引き起こされる動作は未定義です。

戻り値

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


EAGAIN

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


EDEADLK

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

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


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 を持つロックを獲得した所有者が終了すると、次の所有者が EOWNERDEAD を持つロックを獲得します。


ENOTRECOVERABLE

獲得しようとしている mutex は、ロックの保持中に終了した前の所有者によって回復不能にされた状態を保護しています。mutex は獲得されませんでした。ロックが以前に EOWNERDEAD を指定されて獲得され、所有者が状態をクリーンアップできず、mutex の状態を整合させないで mutex をロック解除した場合に、この状況が発生します。


ENOMEM

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

mutex のロック解除

pthread_mutex_unlock(3THR)

pthread_mutex_unlock(3THR) は、mutex が指す mutex のロックを解除します。(Solaris スレッドについては、「mutex_unlock(3THR)」を参照)。


プロトタイプ:
int	pthread_mutex_unlock(pthread_mutex_t *mutex);

#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_mutex_unlock(&mutex); /* mutex を解除する */

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

戻り値

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


EPERM

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

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

pthread_mutex_trylock(3THR)

pthread_mutex_trylock(3THR) は、mutex が指す mutex のロックを試みます。(Solaris スレッドについては、「mutex_trylock(3THR)」を参照)。


プロトタイプ:
int	pthread_mutex_trylock(pthread_mutex_t *mutex);

#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_mutex_trylock(&mutex); /* mutex のロックを試みる */

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

戻り値

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


EBUSY

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


EAGAIN

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

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


EOWNERDEAD

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

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

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

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


ENOTRECOVERABLE

獲得しようとしている mutex は、ロックの保持中に終了した前の所有者によって回復不能にされた状態を保護しています。mutex は獲得されませんでした。ロックが以前に EOWNERDEAD を指定されて獲得され、所有者が状態をクリーンアップできず、mutex の状態を整合させないで mutex をロック解除した場合に、この状況が発生します。


ENOMEM

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

mutex の削除

pthread_mutex_destroy(3THR)

pthread_mutex_destroy(3THR) は、mp が指す mutex に関連するすべての状態を削除します。(Solaris スレッドについては、「mutex_destroy(3THR)」を参照)。


プロトタイプ:
int	pthread_mutex_destroy(pthread_mutex_t *mp);

#include <pthread.h>

pthread_mutex_t mp;
int ret;

ret = pthread_mutex_destroy(&mp); /* mutex を削除する */

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

戻り値

正常終了時は 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 つの関数は、相互排他 (mutex) ロックをそれぞれ別の目的で使用しています。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);

 

 

 

 

/* リソース 1 を使用 */ 

 

pthread_mutex_lock(&m2);

 

 

/* リソース 1 と 2 を使用 */ 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

pthread_mutex_lock(&m2);

 

 

/* リソース 2 を使用 */ 

 

pthread_mutex_lock(&m1);

 

 

/* リソース 1 と 2 を使用 */ 

 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


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

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

ただし、この方法は常に使用できるとは限りません。規定と違う順序で相互排他ロックを獲得しなければならないこともあるからです。そのような状況でデッドロックを防ぐには、pthread_mutex_trylock() を使用します。デッドロックが避けられないような事態が生じた場合は、ある 1 つのスレッドが現在保持している 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 を慎重にロックしなければなりません。これは、mutex の 1 が解放されるまで待つとすると、スレッド 1 との間にデッドロックの関係が生じる恐れがあるからです。

これを防ぐため、スレッド 2 は pthread_mutex_trylock() を呼び出し、mutex がロックされていなければロックします。ロックされていれば、スレッド 2 はただちにエラーを返します。その時点で、スレッド 2 は mutex の 2 を解放しなければなりません。その結果、スレッド 1 は mutex の 2 をロックでき、最終的には mutex の 1 と 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 の位置から開始されるので、常にリストの順序でロックされます。このため、デッドロックは決して発生しません。

目的のノードが見つかった時は、この変更がそのノードと直前のノードの両方に影響を与えるため、両方をロックします。直前のノードのロックが常に最初に獲得されるので、ここでもデッドロックの心配はありません。例 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;

例 4-7 では 2 つのノードをロックし、両方のノードに対してある操作を行なっている C コードを示します。


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


void Hit Neighbor(node2_t *me) {
    while (1) {
        pthread_mutex_lock(&me->lock);
        if (pthread_mutex_lock(&me->link->lock)!= 0) {
            /* ロック失敗 */             
            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);
}