4. 条件変数(2/3)
4.2 スレッド起動時の注意と条件変数の関わり
条件変数を使ってスレッドを操作する場合、いろいろと気を使わないといけない箇所があります。broadcastやsignalを送信した時点で条件変数でサスペンドしているスレッドだけがsignalを受信でき、それ以外のスレッドはsignalを受け止めそこないます。その結果、signal受信漏れのスレッドがずっとサスペンドしてしまう事があります(この問題というか仕様は、pthreadを意味無く難しくさせていて、とっつきにくいものにさせていると思います)。
回避するにはsignalを送信しようとした時点で、スレッドがどのような状態にあるのかを把握しなくてはいけません。
個人的にもそうでしたが、一般的に、スレッドの生成処理、及びmutexと条件変数の使用方法はとても分かりづらいです。頑張って理解してください。
4.2.1 スレッド生成と引数の変化の同期
下記は条件変数を使用したサンプルです。引数によって起動するスレッド数を調整できます。スレッドは一旦pthread_cond_waitによってサスペンドします。呼び出し元(main関数)がpthread_cond_broadcastする事で一斉に起きて、処理(ココではfprintf)を行います。
/* gcc cond_test.c -o cond_test -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; int main( int argc, char ** argv ) { pthread_t pt[THREAD_MAX]; int thr_count = 0; int i; if( argc < 2 ) return( 1 ); thr_count = atol( argv[1] ); if( thr_count <= 0 || thr_count > THREAD_MAX ) { return( 1 ); } for( i = 0; i < thr_count; i ++ ) { pthread_create( &pt[i], 0, temp_func, &i ); } sleep( 1 ); pthread_cond_broadcast( &cond ); for( i = 0; i < thr_count; i ++ ) { pthread_join( pt[i], 0 ); } return 0; } void * temp_func( void * arg ) { int index = *( int * )arg; fprintf( stdout, "start thread index:[%d] input!!\n", index ); pthread_mutex_lock( &mutex ); pthread_cond_wait( &cond, &mutex ); pthread_mutex_unlock( &mutex ) ; fprintf( stdout, "end thread index:[%d] output!!\n", index ); return 0; }
上記サンプルには問題があります。上記を動かす環境によって結果が異なるのですが、"thread index:"の値が同一になる時があります。スレッド側は変数iのポインタ(つまりアドレス)から値を得ている間に、既に呼び出し元はその変数iを次々に変えているために発生します。つまりfor文の動きとスレッド生成の動きに同期が取れていないのです。
pthread_createが解放され戻り値が戻ってくる時、実際のスレッドがどこまで進んでいるのか、仕様上決まっているわけではありません。これはプログラムのバグですが、環境によって出たり出なかったりする、再現が難しい種類のバグでしょう。
経験上、1CPUの環境では出ますが、4CPU環境などでは出にくいです。
4.2.2 引数を実値として利用
下記のサンプルは、上記サンプルから2行だけ変更したものです。スレッドの引数には変数のアドレスが設定する事を想定してますが、そこを実値として利用しています。
/* gcc cond_test_2nd.c -o cond_test_2nd -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_cond_t cond; int main( int argc, char ** argv ) { pthread_t pt[THREAD_MAX]; int thr_count = 0; int i; if( argc < 2 ) return( 1 ); thr_count = atol( argv[1] ); if( thr_count <= 0 || thr_count > THREAD_MAX ) { return( 1 ); } pthread_mutex_init( &mutex, 0 ); pthread_cond_init ( &cond, 0 ); for( i = 0; i < thr_count; i ++ ) { pthread_create( &pt[i], 0, temp_func, ( void * )i ); } sleep( 1 ); pthread_cond_broadcast( &cond ); for( i = 0; i < thr_count; i ++ ) { pthread_join( pt[i], 0 ); } return 0; } void * temp_func( void * arg ) { int index = ( int )arg; fprintf( stdout, "start thread index:[%d] input!!\n", index ); pthread_mutex_lock( &mutex ); pthread_cond_wait( &cond, &mutex ); pthread_mutex_unlock( &mutex ) ; fprintf( stdout, "end thread index:[%d] output!!\n", index ); return 0; }
32bit環境ではワーニングレベルを最大にしてコンパイルしても警告がでませんが、64bit環境では警告が出ます。これは32bitではintが4バイトでポインタ(つまりアドレス)も4バイトなのに対し、64bit環境ではintが4バイトでポインタが8バイトだからです。サイズが異なる物同士で強引なキャストが行われているため、警告が出るというわけです。
改訂版は下記の通りです。
/* gcc cond_test_3rd.c -o cond_test_3rd -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 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 ); sleep( 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 ); pthread_cond_wait( &cond, &mutex ); pthread_mutex_unlock( &mutex ) ; fprintf( stdout, "end thread index:[%d] output!!\n", index ); return 0; }
スレッドの生成付近で同期変数と条件変数を使用し、引数を取得するまで呼び出し元を条件変数でロックしています。取得後、呼び出し元の条件変数に対してcond_signalを実行し、呼び出し元が処理を続行します。
