pthreads (7) condition variable

続いては、condition variable(状態変数)。pthreadの同期機能の一つで、スレッドをイベント待ち状態にしたり、イベント待ち状態のスレッドを起こしたりするキーに使う。「状態変数」と言ってもそれ自体の値を見てどうこうするわけじゃないから注意・・・というか、分かりにくい。なんで状態変数っていうんだろ。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREADSNUM 6
#define ITERATION 40
#define THREASHOLD 100

void func(int *);
void watch(int *);

int res=0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main(void){
     pthread_t thread[THREADSNUM];
     int i;
     for(i=0; i<THREADSNUM-1; i++){
          pthread_create(&thread[i], NULL, (void *)func, &i);
     }
     pthread_create(&thread[i], NULL, (void *)watch, &i);
     for(i=0; i<THREADSNUM; i++){
          pthread_join(thread[i], NULL);
     }
     printf("end: res=%d\n", res);
     free(mutex);
     return 0;
}
void func(int *p)
{
     int i, tmp, id=*p;
     for(i=0; i<ITERATION; i++){
          pthread_mutex_lock(&mutex);
          printf("thread %d: func res=%d\n", id, res);
          if(res == THREASHOLD){
               printf("thread %d: reach threshold.\n", id);
               pthread_cond_signal(&cond);
               printf("thread %d: wakeup.\n", id);
          }
          tmp = res;
          usleep(1);
          res = tmp + 1;
          pthread_mutex_unlock(&mutex);
     }
}

void watch(int *p)
{
     int id = *p;
     printf("thread %d: watch begin.\n", id);
     pthread_mutex_lock(&mutex);
     while(res < THREASHOLD){
          pthread_cond_wait(&cond, &mutex);
     }
     pthread_mutex_unlock(&mutex);
     printf("thread %d: watch end.\n", id);
}

watch()では、目的の状態(res < THREASHOLD)になるまでpthread_cond_wait()によって待ちます。この関数が実行されると、取得していたmutexをuncockし、スレッドはサスペンドします。ここで、pthread_cond_wait()が実行される前にmutexはあらかじめロックしておく必要があります。

一方、実際の処理を行っているfunc()では、目的の状態(res < THREASHOLD)になったらpthread_cond_signal()によって、その状態変数で待っているスレッドを起こします。

watch()の中で

     while(res < THREASHOLD){
          pthread_cond_wait(&cond, &mutex);
     }

という部分があります。何でwhile?ifでいいじゃん、という気もするのですが、オライリーの本によれば偽wakeupの可能性を考慮してwhileループにしているそうです。確かにpthread_cond_signal()は任意のタイミングで出せるので、watch()が期待する閾値に達していなくても起こすことはできます。なので起きた後にも一度自分自身で状態を確認する必要があるということでしょう。

で、実行結果は以下のように。

thread 0: func res=0
thread 1: func res=1
thread 5: watch begin.
thread 2: func res=2
thread 3: func res=3
..
thread 0: func res=99
thread 4: func res=100
thread 4: reach threshold.
thread 4: wakeup.
thread 1: func res=101
thread 2: func res=102
thread 3: func res=103
thread 5: watch end.
thread 0: func res=104
..
thread 4: func res=199
end: res=200