mutex の使用
mutex は、相互排他ロックです。 このロックを保持できるのは、1 つのスレッドだけです。
mutex は、データまたはその他のリソースを同時アクセスから保護するために使用されます。 mutex には、mutex の特性を指定する属性があります。
mutex 属性オブジェクト
スレッドと同様、mutex は属性オブジェクトを用いて作成されます。 mutex 属性オブジェクトは抽象的なオブジェクトであり、POSIX オプションのインプリメントの仕方に応じて、 複数の属性があります。 これは、型 pthread_mutexattr_t の変数を使用してアクセスされます。AIX® では、 pthread_mutexattr_t データ型はポインターですが、他のシステムでは、構造体あるいは他のデータ型の可能性もあります。
mutex 属性オブジェクトの作成と破棄
mutex 属性オブジェクトは、 pthread_mutexattr_init サブルーチンによってデフォルト値に初期化されます。 属性は、サブルーチンによって操作されます。 スレッド属性オブジェクトは、 pthread_mutexattr_destroy サブルーチンによって破棄されます。 このサブルーチンは、スレッド・ライブラリーのインプリメンテーションに応じて、pthread_mutexattr_init サブルーチンによって動的に割り当てられたストレージを解放することができます。
pthread_mutexattr_t attributes;
/* the attributes object is created */
...
if (!pthread_mutexattr_init(&attributes)) {
/* the attributes object is initialized */
...
/* using the attributes object */
...
pthread_mutexattr_destroy(&attributes);
/* the attributes object is destroyed */
}同じ属性オブジェクトを使用して、複数の mutex を作ることができます。 mutex 作成の間で、変更することもできます。 mutex が作成されると、 属性オブジェクトを、作成された mutex に影響を与えずに破棄することができます。
mutex 属性
以下の mutex 属性が定義されています。
| 属性 | 説明 |
|---|---|
| Protocol | mutex の優先順位の逆転を避けるために使用されるプロトコルを指定します。 この属性は、優先順位継承または優先順位保護の POSIX オプション のどちらかに依存します。 |
| Process-shared | mutex のプロセス共用を指定します。 この属性は、プロセス共用の POSIX オプションに依存します。 |
これらの属性について詳しくは、スレッド・ライブラリー・オプションおよび同期スケジューリングを参照してください。
mutex の作成と破棄
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
...
pthread_mutexattr_init(&attr);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);pthread_mutex_t mutex;
...
pthread_mutex_init(&mutex, NULL);作成された mutex の ID は、 mutex パラメーターを使用してコール側のスレッドに戻されます。 mutex ID は隠しオブジェクトであり、 その型は pthread_mutex_t です。 AIX では、pthread_mutex_t データ型は構造体ですが、 他のシステムでは、ポインターまたは他のデータ型である場合もあります。
mutex は 1 回だけ作成されます。 しかし、pthread_mutex_init サブルーチンを同じ mutex パラメーターで複数回呼び出す (例えば、並行して同じコードを実行している 2 つのスレッドで) ことは避けてください。 mutex 作成の際に固有性を確保する方法には、以下があります。
- この mutex を使用する他のスレッドを作成する前に、 例えば、初期スレッドで pthread_mutex_init サブルーチンを呼び出す。
- ワンタイム初期化ルーチン内で、pthread_mutex_init サブルーチンを呼び出す。 詳しくは、ワンタイム初期化を参照してください。
- PTHREAD_MUTEX_INITIALIZER 静的初期化マクロによって初期化された静的 mutex を使用する。 mutex は、デフォルトの属性を持ちます。
pthread_mutex_t mutex;
...
for (i = 0; i < 10; i++) {
/* creates a mutex */
pthread_mutex_init(&mutex, NULL);
/* uses the mutex */
/* destroys the mutex */
pthread_mutex_destroy(&mutex);
}スレッド間で共用できる他のシステム・リソースと同様に、 スレッドのスタックに割り当てられた mutex は、スレッドが終了する前に破棄する必要があります。 スレッド・ライブラリーが mutex のリンク・リストを保守しています。mutex が割り当てられているスタックが解放されると、そのリストは破棄されます。
mutex のタイプ
- PTHREAD_MUTEX_DEFAULT または PTHREAD_MUTEX_NORMAL
- この場合、 最初にアンロックを行わずに 同じ pthread が pthread_mutex_lock サブルーチンを使用して 2 回目のロックを行おうとすると、デッドロックが発生します。 これがデフォルトのタイプです。
- PTHREAD_MUTEX_ERRORCHECK
- 最初に mutex のアンロックを行わずに同じ thread が同じ mutex のロックを複数回試行した場合に、 ゼロ以外の値が戻されることにより、 デッドロックが回避されます。
- PTHREAD_MUTEX_RECURSIVE
- pthread_mutex_lock サブルーチンを使用して、 同じ pthread が mutex のロックを再帰的に行えるため、 デッドロックが発生したり、 pthread_mutex_lock からゼロ以外の値が戻されることはありません。 この同じ pthread は、pthread_mutex_lock サブルーチンに行ったのと同じ回数、 pthread_mutex_unlock サブルーチンを呼び出して、 他の pthread が使用できるように mutex をアンロックしなければなりません。
mutex 属性は、最初の作成時に、 デフォルト・タイプである PTHREAD_MUTEX_NORMAL になっています。 mutex の作成後、pthread_mutexattr_settype API ライブラリー呼び出しを使用して、タイプを変更できます。
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);
struct {
int a;
int b;
int c;
} A;
f()
{
pthread_mutex_lock(&mutex);
A.a++;
g();
A.c = 0;
pthread_mutex_unlock(&mutex);
}
g()
{
pthread_mutex_lock(&mutex);
A.b += A.a;
pthread_mutex_unlock(&mutex);
}mutex のロックとアンロック
mutex は、ロックとアンロックの 2 つの状態を持つ簡単なロックです。 作成時、mutex はアンロック状態です。 pthread_mutex_lock サブルーチンは、以下の条件下で、指定された mutex をロックします。
- mutex がアンロック状態の場合、このサブルーチンはそれをロックします。
- mutex が既に他のスレッドによってロックされている場合、 このサブルーチンは、mutex がアンロックされるまでコール側のスレッドをブロックします。
- mutex が既にコール側のスレッドによってロックされている場合、サブルーチンは、mutex のタイプによっては、永久にブロックされるか、またはエラーが戻されることがあります。
pthread_mutex_trylock サブルーチンは、pthread_mutex_lock サブルーチンと似た動きをしますが、次の条件で、コール側のスレッドをブロックしません。
- mutex がアンロック状態の場合、このサブルーチンはそれをロックします。
- mutex が既に任意のスレッドによってロックされている場合、 このサブルーチンはエラーを戻します。
mutex をロックしたスレッドは、 しばしば mutex のオーナー と呼ばれます。
pthread_mutex_unlock サブルーチンは、コール側の mutex が 所有している場合は、次の条件で、指定された mutex をアンロック状態にリセットします。
- mutex が既にアンロックされている場合、このサブルーチンはエラーを戻します。
- mutex がコール側のスレッドによって所有されていた場合、 このサブルーチンは mutex をアンロックします。
- mutex が他のスレッドによって所有されていた場合、サブルーチンは、mutex のタイプによっては、 エラーが戻されるか、または mutex がアンロックされることがあります。 mutex は通常、 同じ pthread によってロックとアンロックが行われるため、 このような mutex のアンロックは望ましくありません。
ロックには取り消しポイントがないので、mutex を待っていた間にブロックされたスレッドは取り消すことができません。 したがって、mutex を使用するのは、並行アクセスからのデータの保護と同様に、短時間にとどめることをお勧めします。 詳しくは、取り消しポイントおよびスレッドの取り消しを参照してください。
mutex によるデータの保護
mutex は、他のスレッド同期関数を作成するための低レベルのプリミティブとして、 またはデータ保護ロックとして働くように作られています。 ロング・ロックおよびライター優先リーダー/ライター・ロックのインプリメントについて詳しくは、mutex の使用を参照してください。
mutex 使用例
/* the initial thread */
pthread_mutex_t mutex;
int i;
...
pthread_mutex_init(&mutex, NULL); /* creates the mutex */
for (i = 0; i < num_req; i++) /* loop to create threads */
pthread_create(th + i, NULL, rtn, &mutex);
... /* waits end of session */
pthread_mutex_destroy(&mutex); /* destroys the mutex */
...
/* the request handling thread */
... /* waits for a request */
pthread_mutex_lock(&db_mutex); /* locks the database */
... /* handles the request */
pthread_mutex_unlock(&db_mutex); /* unlocks the database */
...初期スレッドは、mutex とすべての要求処理スレッドを作成します。 mutex は、スレッドのエントリー・ポイント・ルーチンのパラメーターを使用して、 スレッドに渡されます。 実際のプログラムでは、mutex のアドレスは、 作成されたスレッドに渡された、さらに複雑なデータ構造体のフィールドである場合があります。
デッドロックの回避
- デフォルト・タイプである PTHREAD_MUTEX_NORMAL で作成された mutex は、 同じ pthread によって再ロックすると必ずデッドロックが発生します。
- mutex を逆の順序でロックした場合、
アプリケーションでデッドロックが発生する可能性があります。
例えば、次のコード・フラグメントは、
スレッド A とスレッド B の間にデッドロックを発生させます。
/* Thread A */ pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2); /* Thread B */ pthread_mutex_lock(&mutex2); pthread_mutex_lock(&mutex1); - アプリケーションでは、
リソース・デッドロックというデッドロックが発生する場合があります。
例:
このアプリケーションにはstruct { pthread_mutex_t mutex; char *buf; } A; struct { pthread_mutex_t mutex; char *buf; } B; struct { pthread_mutex_t mutex; char *buf; } C; use_all_buffers() { pthread_mutex_lock(&A.mutex); /* use buffer A */ pthread_mutex_lock(&B.mutex); /* use buffers B */ pthread_mutex_lock(&C.mutex); /* use buffer C */ /* All done */ pthread_mutex_unlock(&C.mutex); pthread_mutex_unlock(&B.mutex); pthread_mutex_unlock(&A.mutex); } use_buffer_a() { pthread_mutex_lock(&A.mutex); /* use buffer A */ pthread_mutex_unlock(&A.mutex); } functionB() { pthread_mutex_lock(&B.mutex); /* use buffer B */ if (..some condition) { use_buffer_a(); } pthread_mutex_unlock(&B.mutex); } /* Thread A */ use_all_buffers(); /* Thread B */ functionB();スレッド Aとスレッド Bという 2 つのスレッドがあります。 まずスレッド Aの実行が開始されてから、 その少し後でスレッド Bが開始されます。スレッド Aが use_all_buffers() を実行し、 A.mutex が正常にロックされると、 次に B.mutex のロックを試行したときに、 これがスレッド Bによって既にロックされているためブロックされます。スレッド Bが functionB を実行しており、かつ、スレッド Aがブロックされている間にsome_conditionが発生すると、スレッド Bは、A.mutex の獲得試行もブロックします。それがスレッド Aによって既にロックされているためです。この結果、デッドロックが生じます。このデッドロックを解決するには、 リソースを使用する前に、 必要とするすべてのリソース・ロックを各スレッドで獲得してください。 ロックを獲得できなければ、それらを解除して再度開始する必要があります。
mutex と競争状態
相互排他ロック (mutex) を使用することにより、 競争状態によるデータの不整合を防ぐことができます。 競争状態は、2 つ以上のスレッドが同じメモリー領域の操作を実行する必要があるときにしばしば発生しますが、 計算の結果は、それらの操作の実行順序によって決まります。
move X, REG
inc REG
move REG, X上記の例の両方のスレッドが 2 つの CPU で同時に実行される場合、 またはスケジューリングによってスレッドがそれぞれの命令を交互に実行する場合は、 次のようなステップになる可能性があります。
- スレッド A が最初の命令を実行して、X (これは 1)
をスレッド A のレジスターに書き込みます。 その後、スレッド B が実行して、X (これは 1) をスレッド B のレジスターに書き込みます。 次の例は、実行後のレジスターとメモリー X の内容を示しています。
スレッド A レジスター = 1 スレッド B レジスター = 1 Memory X = 1 - スレッド A が 2 番目の命令を実行して、
そのレジスターの内容を 2 に増分します。そして、スレッド B がそのレジスターの内容を 2 に増分します。
メモリー X にはなにも移動されないので、メモリー X は変化しません。 次の例は、実行後のレジスターとメモリー X の内容を示しています。
スレッド A レジスター = 2 スレッド B レジスター = 2 Memory X = 1 - スレッド A がそのレジスターの内容 (現在は 2)
をメモリー X に移動します。
その後で、スレッド B がそのレジスターの内容 (これも、現在は 2) をメモリー X に移動して、
スレッド A の値に上書きします。
次の例は、実行後のレジスターとメモリー X の内容を示しています。
スレッド A レジスター = 2 スレッド B レジスター = 2 メモリー X = 2
多くの場合、スレッド A とスレッド B は 3 つの命令を順次に実行し、 結果は予想どおりに 3 になります。 競争状態は断続的に起こるので、 通常は検出することが困難です。
このような競争状態を避けるために、それぞれのスレッドは、 カウンターのアクセスやメモリー X の更新の前にデータをロックする必要があります。 例えば、スレッド A がロックしてからカウンターを更新すると、メモリー X に値 2 が残ります。 スレッド A がロックを解除した後、スレッド B は、 ロックしてから、X の初期値を 2 として、予想される結果である 3 に増分するというカウンターの更新を行います。