SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

pthreadについて

pthreadについて(条件変数・モデル)

pthreadにおける条件変数の扱い方、気をつける点、簡単なアプリケーション


4. 条件変数(2/3)

4.2 スレッド起動時の注意と条件変数の関わり

 条件変数を使ってスレッドを操作する場合、いろいろと気を使わないといけない箇所があります。broadcastやsignalを送信した時点で条件変数でサスペンドしているスレッドだけがsignalを受信でき、それ以外のスレッドはsignalを受け止めそこないます。その結果、signal受信漏れのスレッドがずっとサスペンドしてしまう事があります(この問題というか仕様は、pthreadを意味無く難しくさせていて、とっつきにくいものにさせていると思います)。

 回避するにはsignalを送信しようとした時点で、スレッドがどのような状態にあるのかを把握しなくてはいけません。

 個人的にもそうでしたが、一般的に、スレッドの生成処理、及びmutexと条件変数の使用方法はとても分かりづらいです。頑張って理解してください。

4.2.1 スレッド生成と引数の変化の同期

 下記は条件変数を使用したサンプルです。引数によって起動するスレッド数を調整できます。スレッドは一旦pthread_cond_waitによってサスペンドします。呼び出し元(main関数)がpthread_cond_broadcastする事で一斉に起きて、処理(ココではfprintf)を行います。

cond_test.c
/* 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行だけ変更したものです。スレッドの引数には変数のアドレスが設定する事を想定してますが、そこを実値として利用しています。

cond_test_2nd.c
/* 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バイトだからです。サイズが異なる物同士で強引なキャストが行われているため、警告が出るというわけです。

 改訂版は下記の通りです。

cond_test_3rd.c
/* 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を実行し、呼び出し元が処理を続行します。

次のページ
4. 条件変数(3/3)

この記事は参考になりましたか?

pthreadについて連載記事一覧

もっと読む

この記事の著者

赤松 エイト(エイト)

(株)DTSに勤てます。WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。mixiもやってまして、こちらは子育てとか日々の日記メインです。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1894 2008/08/20 15:59

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー