4. 条件変数(3/3)
4.2.3 スレッド生成と条件変数の同期
しかし、更に前ページのプログラムには危険な箇所が隠れてます。呼び出し元(main関数内)のsleep関数をコメントアウトすると、環境によってはプログラムはサスペンドします。
上記サンプルは、呼び出し元がpthread_cond_broadcastを行う事で生成されたスレッドが一斉に起きて処理(この場合はfprintfを実行)します。しかし一番最後に生成されたスレッドは、まだpthread_cond_waitにたどり着いていない時にpthread_cond_broadcastが届く可能性があります。
こうなると最後に生成されたスレッドはpthread_cond_waitで待ち続け、呼び出し元はpthread_joinで待ち続けるという、いわゆるデッドロック状態に陥ります。
この改訂版は下記の通りです。
/* gcc cond_test_4th.c -o cond_test_4th -W -Wall -lpthread */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define THREAD_MAX 256 void * temp_func( void * arg ); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t ready_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ready_cond = PTHREAD_COND_INITIALIZER; int start_flg = 0; int main( int argc, char ** argv ) { pthread_t pt[THREAD_MAX]; int all_thr_count = 0; int i; if( argc < 2 ) return( 1 ); all_thr_count = atol( argv[1] ); if( all_thr_count <= 0 || all_thr_count > THREAD_MAX ) { return( 1 ); } pthread_mutex_lock( &ready_mutex ); for( i = 0; i < all_thr_count; i ++ ) { pthread_create( &pt[i], 0, temp_func, &i ); pthread_cond_wait( &ready_cond, &ready_mutex ); } pthread_mutex_unlock( &ready_mutex ); start_flg = 1; pthread_cond_broadcast( &cond ); for( i = 0; i < all_thr_count; i ++ ) { pthread_join( pt[i], 0 ); } return 0; } void * temp_func( void * arg ) { int index = *( int * )arg; pthread_cond_signal( &ready_cond ); fprintf( stdout, "start thread index:[%d] input!!\n", index ); pthread_mutex_lock( &mutex ); while( start_flg == 0 ) { pthread_cond_wait( &cond, &mutex ); } pthread_mutex_unlock( &mutex ) ; fprintf( stdout, "end thread index:[%d] output!!\n", index ); return 0; }
グローバルな変数を使ってしまうのは忍びないですが、スレッド内で条件変数を参照する際、更に条件を設定します(start_flg == 0)。呼び出し元からはpthread_cond_broadcastを発行する前に条件式を外しておきます。こうする事で最後のスレッドはpthread_cond_waitを行うことなく処理を続行できます。
もう1つの危険箇所がスレッド内のpthread_cond_signalと呼び出し元のpthread_cond_waitです。
スレッド内で引数を受け取ったら呼び出し元向けにpthread_cond_signalを発行していますが、果たして呼び出し元がpthread_cond_waitで待ち状態にいるのか、不明です。呼び出し元がpthread_cond_waitに居ない時にスレッドがpthread_cond_signalを発行すると、呼び出し元はsignalを取りそこない、その結果pthread_cond_waitでデッドロックが発生します。
上記プログラムを何度か動かすと、環境によっては、スレッドが指定した数まで起動しないでサスペンドする場合が発生します。私の環境では平均で5回に1回程再現します。
引数を受け取る際には呼び出し元がpthread_cond_waitで待ち状態に入っている事を保障された状態で処理し、pthread_cond_signalを発行するようにします。
/* gcc cond_test_5th.c -o cond_test_5th -W -Wall -lpthread */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define THREAD_MAX 256 void * temp_func( void * arg ); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t ready_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ready_cond = PTHREAD_COND_INITIALIZER; int start_flg = 0; int main( int argc, char ** argv ) { pthread_t pt[THREAD_MAX]; int all_thr_count = 0; int i; if( argc < 2 ) return( 1 ); all_thr_count = atol( argv[1] ); if( all_thr_count <= 0 || all_thr_count > THREAD_MAX ) { return( 1 ); } pthread_mutex_lock( &ready_mutex ); for( i = 0; i < all_thr_count; i ++ ) { pthread_create( &pt[i], 0, temp_func, &i ); pthread_cond_wait( &ready_cond, &ready_mutex ); } pthread_mutex_unlock( &ready_mutex ); start_flg = 1; pthread_cond_broadcast( &cond ); for( i = 0; i < all_thr_count; i ++ ) { pthread_join( pt[i], 0 ); } return 0; } void * temp_func( void * arg ) { pthread_mutex_lock( &ready_mutex ); int index = *( int * )arg; pthread_cond_signal( &ready_cond ); pthread_mutex_unlock( &ready_mutex ); fprintf( stdout, "start thread index:[%d] input!!\n", index ); pthread_mutex_lock( &mutex ); while( start_flg == 0 ) { pthread_cond_wait( &cond, &mutex ); } pthread_mutex_unlock( &mutex ) ; fprintf( stdout, "end thread index:[%d] output!!\n", index ); return 0; }
上記から、スレッドはただ生成するだけでも決して油断ならない事が分かったと思います。再現性の薄いバグが混在しやすく、回避するにはプログラムも泥臭くなりやすいです。常に呼び出し元と呼び出し先の状態を気に留めながら、条件変数を使用し慎重なコーディングを心がけるようにしましょう。
