fc2ブログ

pthread_cond_timedwaitを使ってみる

他のスレッドの実行を待って処理したい場合なんかに、普通だと
pthread_joinとかをして待つと思う。

が、戻り値が必要なかったり、スレッド内のある地点まで進んでいれば
別に最後まで待つ必要が無かったりした場合、pthread_joinで待つのは
無駄とも思える。

または、親スレッドから指示を出すまで子スレッドに止まっていて欲しい
などという要求があったりするかもしれない。

そんなときはpthread_cond_wait(またはpthread_cond_timedwait)と
pthread_cond_signalを使う。

これらの関数群は条件変数を使ってタイミング制御を行うためのもの。

使い方は簡単。

指示待ち状態にしたいところでpthread_cond_wait
(またはpthread_cond_timedwait)しておき、
指示を出す部分でpthread_cond_signalする。

名前の通り、pthread_cond_signalが指示を投げて、
pthread_cond_wait等が動き出すというだけ。

注意点は2つ:
1. pthread_cond_signalはpthread_cond_waitしている一つのスレッドにしか信号を投げることができない。

要するに、複数スレッドがpthread_cond_waitするような場合はpthread_cond_signal
を使うことが出来ない。
投げた場合に複数のうちのどのスレッドになるかはスケジューリング方針によって決まるので、一概には言えない。

なので、複数に投げたいときはpthread_cond_broadcastを使うこと。


2. 条件変数へのアクセスは排他制御が必要

pthread_cond_signalはpthread_cond_waitしているスレッドがない限り何も起こらない。
なので、pthread_cond_signalを投げる瞬間にpthread_cond_waitしているように
コーディングする必要がある。

このために排他制御をうまく利用する。


で、以下がサンプルだが、この例ではスレッドを複数起動し、
子スレッドの全部が終了するのを待つ。

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


///// constants /////
#define THR_NUM      10        // number of threads
#define TIMEOUT      5        // cond_timedwait timeout

///// prototype declarations /////
void *mainThread(void *arg);
void *childThread(void *arg);


///// global variables /////
int giThrNum;              // number of threads
pthread_mutex_t gMutexThrNum;      // condition variable
pthread_cond_t gCondThrNum;        // mutex varable


int main(int argc, char *argv[])
{
  int iFbRet;              // return value of function block
  pthread_t tId;            // thread ID
  void *pRet;              // return value of thread

  pthread_mutex_init(&gMutexThrNum, NULL);
  pthread_cond_init(&gCondThrNum, NULL);

  iFbRet = pthread_create(&tId, NULL, mainThread, &iFbRet);
  printf("[main] pthread_create: %d\n", iFbRet);

  pthread_join(tId, &pRet);

  return 0;
}


//-----
void *mainThread(void *arg)
{
  int iFbRet;              // return value of function block
  int iCnt;              // counter
  pthread_t tId[THR_NUM];        // thread ID
  struct timespec timeout;      // timeout

  // initialize number of threads
  giThrNum = 0;

  for (iCnt = 0; iCnt < THR_NUM; iCnt++)
  {
    iFbRet = pthread_create(&tId[iCnt], NULL, childThread, (void *)iCnt);
    printf("[mainThread] pthread_create #%d: %d\n", iCnt, iFbRet);

    // update number of threads
    pthread_mutex_lock(&gMutexThrNum);
    giThrNum++;
    pthread_mutex_unlock(&gMutexThrNum);
  }

  // wait for all thread finish
  pthread_mutex_lock(&gMutexThrNum);
  while (giThrNum > 0)
  {
    printf("[mainThread] In while() before cond_timedwait\n");
    timeout.tv_sec = time(NULL) + TIMEOUT;
    timeout.tv_nsec = 0;
    iFbRet = pthread_cond_timedwait(&gCondThrNum, &gMutexThrNum, &timeout);
    switch (iFbRet)
    {
    case ETIMEDOUT:
      printf("[mainThread] pthread_cond_timedwait() timed out.\n");
      pthread_exit(NULL);
    case EINVAL:
      printf("[mainThread] pthread_cond_timedwait() returned EINVAL.\n");
      pthread_exit(NULL);
    default:
      break;
    }
  }
  pthread_mutex_unlock(&gMutexThrNum);

  pthread_exit(NULL);
}

//-----
void *childThread(void *arg)
{
  printf("[childThread:%X] %d begin\n", (unsigned int)pthread_self(), (int)arg);
  
  pthread_mutex_lock(&gMutexThrNum);
  giThrNum--;
  pthread_cond_signal(&gCondThrNum);
  pthread_mutex_unlock(&gMutexThrNum);

  printf("[childThread:%X] %d end\n", (unsigned int)pthread_self(), (int)arg);

  pthread_exit(NULL);
}

当たり前だけど、-lpthreadしないとリンクできない。
いや、出来る環境もあるけど。

起動しているスレッド数はgiThrNumで管理している。
この、起動スレッド数へのアクセスを排他制御することでsignalを受け取れるように
なっている。

スレッド終了待ちwhileに入る前にlockするので、このwhileの中でsignalが投げられることはない。

もしスレッド終了待ちwhileに入る前に全部終了していれば、giThrNumが0に
なるので特に問題ない。

pthread_cond_timedwaitは内部でunlockして待ち状態になる。
子スレッド内ではlock状態でsignalを投げているので、親スレッドで
pthread_cond_timedwaitに入らない限りsignalが投げられることはない。


と、ここまで書いてバグに気づく。
このままではメモリリークしてしまう。

pthread_joinしない場合はpthread_detachするかpthread_create時に
属性detachstateにしておかなければならない。

まぁ、今回のサンプルでは大目に見てくれということで。

管理者にだけ表示を許可する

pthread_cond_timedwaitの検索をして来る人が多いので、もう少し頑張って説明してみることにした。 と言っても、自分も完全に分かっているわけでは...

2009.08.24 08:55 pLog