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 属性が定義されています。
| 属性 | 説明 |
|---|---|
| プロトコル | 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();thread Aとthread Bの 2 つのスレッドがあります。Thread Bは最初に実行を開始し、その後すぐにthread Aが開始します。thread Aが use_all_buffers () を実行し、 A.mutexを正常にロックした場合、thread Bが既にロックしているため、 B.mutexをロックしようとするとブロックされます。thread Bが functionB を実行している間、thread Aがブロックされている間にsome_conditionが発生すると、thread Bは、thread Aによって既にロックされている A.mutexの獲得の試行もブロックするようになります。 この結果、デッドロックが発生します。このデッドロックを解決するには、 リソースを使用する前に、 必要とするすべてのリソース・ロックを各スレッドで獲得してください。 ロックを獲得できなければ、それらを解除して再度開始する必要があります。
mutex と競争状態
相互排他ロック (mutex) を使用することにより、 競争状態によるデータの不整合を防ぐことができます。 競争状態は、2 つ以上のスレッドが同じメモリー領域の操作を実行する必要があるときにしばしば発生しますが、 計算の結果は、それらの操作の実行順序によって決まります。
move X, REG
inc REG
move REG, X上記の例の両方のスレッドが 2 つの CPU で同時に実行される場合、 またはスケジューリングによってスレッドがそれぞれの命令を交互に実行する場合は、 次のようなステップになる可能性があります。
- スレッド A が最初の命令を実行して、X (これは 1)
をスレッド A のレジスターに書き込みます。 その後、スレッド B が実行して、X (これは 1) をスレッド B のレジスターに書き込みます。 次の例は、実行後のレジスターとメモリー X の内容を示しています。
Thread A Register = 1 Thread B Register = 1 Memory X = 1 - スレッド A は 2 番目の命令を実行し、そのレジスターの内容を 2に増分します。 その後、スレッド B はそのレジスターを 2に増分します。 メモリー Xには何も移動されません。したがって、メモリーは X ままになります。 次の例は、実行後のレジスターとメモリー X の内容を示しています。
Thread A Register = 2 Thread B Register = 2 Memory X = 1 - スレッド A は、レジスターの内容をメモリー Xに移動します。この内容は、現在 2になります。 次に、スレッド B はそのレジスターの内容を ( 2でもある ) メモリー Xに移動し、スレッド Aの値を上書きします。 次の例は、実行後のレジスターとメモリー X の内容を示しています。
Thread A Register = 2 Thread B Register = 2 Memory X = 2
多くの場合、スレッド A とスレッド B は 3 つの命令を順次に実行し、 結果は予想どおりに 3 になります。 競争状態は断続的に起こるので、 通常は検出することが困難です。
この競合状態を回避するには、カウンターにアクセスしてメモリー Xを更新する前に各スレッドがデータをロックする必要があります 例えば、スレッド A がロックを取り、カウンターを更新した場合、スレッドはメモリー X を 2 の値で残します。 スレッド A がロックを解放すると、スレッド B はロックを取り、カウンターを更新し、 2 を X の初期値として取り、それを 3に増分します。