[プログラミング] pthreadのキャンセルに関するメモ

pthread_cancelを使ったプログラムを書いててはまりそうになったのでメモ。
下のコードのように子スレッドでpthread_cond_wait(3)を用いてなんらかの条件が成立するまで待機している関数があるとする。このスレッドをキャンセルしてみる。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;

void * func(void * arg){
  int last_type;
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED , &last_type);
  pthread_mutex_lock(&mutex);
  // cancelation point
  pthread_cond_wait(&cond , &mutex);
  pthread_mutex_unlock(&mutex);
  pthread_setcanceltype(last_type, NULL);
}

int main(){
  pthread_t thread;
  pthread_create(&thread , NULL , func , NULL);
  // funcのpthread_mutex_lockが呼ばれてからpthread_cancelを呼ぶため少し待つ
  sleep(1);
  pthread_cancel(thread);
  pthread_join(thread , NULL);
  int res = pthread_mutex_trylock(&mutex);
  if(res != 0){
    printf("Error pthread_mutex_trylock : %s\n" , strerror(res));
  }else{
    printf("Success pthread_mutex_trylock\n" );
  }
  return 0;
}

すると実行結果は次のようになる。

Error pthread_mutex_trylock : Device or resource busy

こうなる原因としては

  1. 子スレッドのpthread_mutex_lockでmutexのロックがかかる
  2. 子スレッドのpthread_cond_waitでmutexのロックが解除される
  3. mainでのpthread_cancelによって子スレッドがキャンセルされる
  4. キャンセルに伴い子スレッドの待ち状態が解除され、pthread_cond_waitで関連付けられたmutexのロックがかかる
  5. mutexにロックがかかったまま子スレッド終了

となっているためである。

これを回避するためにはpthread_cleanup_pushを用いる。これを使うとスレッドが取り消されたり、終了した際に第一引数のhandlerが第二引数のargを引数として実行される。登録したcleanupハンドラを削除するにはpthread_cleanup_popを呼び出す。

修正したコードは次のようになる

void * func(void * arg){
  int last_type;
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED , &last_type);
  pthread_cleanup_push((void (*) (void *))pthread_mutex_unlock , (void *)&mutex);
  pthread_mutex_lock(&mutex);
  // cancelation point
  pthread_cond_wait(&cond , &mutex);
  pthread_mutex_unlock(&mutex);
  pthread_setcanceltype(last_type, NULL);
  // 引数が0の時は何もしない、0以外の場合ハンドラが呼び出される
  pthread_cleanup_pop(0);
}