13. mutex_type
3章および9章で少し述べましたが、mutexには属性を持つことができます。主にスケジューリングに関する属性について説明してきましたが、それ以外に、mutexのロック時の挙動に対してタイプを設定することができます。
13.1 種類
mutexに設定できるタイプは下記4タイプあります。それぞれの特徴は下記の通りです。
タイプ | ロック済みmutexの再ロック | 未ロックmutexのアンロック | 備考 |
PTHREAD_MUTEX_NORMAL | デッドロック | 未定 | 初期値、条件変数と併用可能 |
PTHREAD_MUTEX_RECURSIVE | 内部カウンタ 1増加 | 内部カウンタ 1減少 | 内部カウンタ0のアンロックはエラー |
PTHREAD_MUTEX_ERRORCHECK | エラー発生 | エラー発生 | |
PTHREAD_MUTEX_DEFAULT | 未定 | 未定 | PTHREAD_MUTEX_NORMALと同じ振る舞い |
なお、pthread_cond系の関数と一緒に使用する場合、タイプはPTHREAD_MUTEX_NORMALでないといけません。次章で各タイプのサンプルで動きを見ます。
13.2 サンプルと動作結果
PTHREAD_MUTEX_NORMAL についてですが、これは今まで沢山使用してきたmutexのことです。PTHREAD_MUTEX_DEFAULT は実装依存ですが、 PTHREAD_MUTEX_NORMAL と同じ動きをするといっても過言ではないでしょう。下記は私の環境での<pthread.h>です。
enum {
・・・
PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
・・・
};
PTHREAD_MUTEX_RECURSIVE のサンプルを動かした結果は下記の通りです。main
関数でロックしている時にスレッドでロックしようとしてもサスペンド状態になります。つまりあくまで同一スレッド内で内部カウンタが作動していて、同一スレッド内でアンロックがすべて終わらないと他スレッドのロックが行われないということが分かります。
/* gcc mutex_recursive.c -o mutex_recursive -g -W -Wall -lpthread */ #define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <unistd.h> #include <pthread.h> pthread_mutex_t mutex; void * func( void * arg ); int main( ) { pthread_t id = 0; pthread_mutexattr_t attr; int status, i; pthread_mutexattr_init( &attr ); status = pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); printf( "in main, pthread_mutexattr_settype:[%d]\n", status ); status = pthread_mutex_init( &mutex, &attr ); printf( "in main, pthread_mutex_init:[%d]\n", status ); for( i = 0, status = 0; i < 4 && status == 0; i ++ ) { status = pthread_mutex_lock( &mutex ); printf( "in main, %d pthread_mutex_lock:[%d][%s]\n", i + 1, status, strerror( status )); sleep( 1 ); } status = pthread_create( &id, 0, func, 0 ); printf( "in main, pthread_create:[%d]\n", status ); sleep( 1 ); for( i = 0, status = 0; i < 5 && status == 0; i ++ ) { status = pthread_mutex_unlock( &mutex ); printf( "in main, %d pthread_mutex_unlock:[%d][%s]\n", i + 1, status, strerror( status )); sleep( 1 ); } pthread_join( id, 0 ); pthread_mutex_destroy( &mutex ); return 0; } void * func( void * arg ) { ( void )arg; int status, i; for( i = 0, status = 0; i < 4 && status == 0; i ++ ) { status = pthread_mutex_lock( &mutex ); printf( "in func, %d pthread_mutex_lock:[%d][%s]\n", i + 1, status, strerror( status )); sleep( 1 ); } for( i = 0, status = 0; i < 5 && status == 0; i ++ ) { status = pthread_mutex_unlock( &mutex ); printf( "in func, %d pthread_mutex_unlock:[%d][%s]\n", i + 1, status, strerror( status )); sleep( 1 ); } return 0; }
guest $./mutex_recursive in main, pthread_mutexattr_settype:[0] in main, pthread_mutex_init:[0] in main, 1 pthread_mutex_lock:[0][Success] in main, 2 pthread_mutex_lock:[0][Success] in main, 3 pthread_mutex_lock:[0][Success] in main, 4 pthread_mutex_lock:[0][Success] in main, pthread_create:[0] in main, 1 pthread_mutex_unlock:[0][Success] in main, 2 pthread_mutex_unlock:[0][Success] in main, 3 pthread_mutex_unlock:[0][Success] in main, 4 pthread_mutex_unlock:[0][Success] in func, 1 pthread_mutex_lock:[0][Success] in main, 5 pthread_mutex_unlock:[1][Operation not permitted] in func, 2 pthread_mutex_lock:[0][Success] in func, 3 pthread_mutex_lock:[0][Success] in func, 4 pthread_mutex_lock:[0][Success] in func, 1 pthread_mutex_unlock:[0][Success] in func, 2 pthread_mutex_unlock:[0][Success] in func, 3 pthread_mutex_unlock:[0][Success] in func, 4 pthread_mutex_unlock:[0][Success] in func, 5 pthread_mutex_unlock:[1][Operation not permitted] guest $
PTHREAD_MUTEX_ERRORCHECK は、PTHREAD_MUTEX_RECURSIVE のソースファイルと異なる点は下記の通り、kindの値だけです。
guest $ diff mutex_recursive.c mutex_check.c 1c1 < /* gcc mutex_recursive.c -o mutex_recursive -g -W -Wall -lpthread */ --- > /* gcc mutex_check.c -o mutex_check -g -W -Wall -lpthread */ 18c18 < status = pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); --- > status = pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_ERRORCHECK ); guest $
しかし動かすと、2回目のロックおよびアンロックでエラーが出力されます。同一スレッドでの二重ロック・二重アンロックのみエラーを検出し、他スレッドはロック待ちになっています。
guest $ ./mutex_check in main, pthread_mutexattr_settype:[0] in main, pthread_mutex_init:[0] in main, 1 pthread_mutex_lock:[0][Success] in main, 2 pthread_mutex_lock:[35][Resource deadlock avoided] in main, pthread_create:[0] in main, 1 pthread_mutex_unlock:[0][Success] in func, 1 pthread_mutex_lock:[0][Success] in main, 2 pthread_mutex_unlock:[1][Operation not permitted] in func, 2 pthread_mutex_lock:[35][Resource deadlock avoided] in func, 1 pthread_mutex_unlock:[0][Success] in func, 2 pthread_mutex_unlock:[1][Operation not permitted] guest $
13.3 所感
正直言ってそれ程実用性はありません。条件変数との併用を考えると、mutexのタイプは初期値にならざるを得ません。しかし単一スレッド内でデッドロックを行っているかチェックする場合には使えそうな機能だと思います。