SpresenseでNuttXのpthreadsを使用しマルチスレッドを学ぶ #7 〜【どうぞどうぞ】を条件変数・バリアで実装する〜


これは何?

前回に引き続きSpresenseでpthreadsインターフェースを使って動作確認します。

今回のテーマ

今回はダチョウ倶楽部さんの【どうぞどうぞ】を今まで学んだpthreadsの関数で実現します。
今回使うpthreadsの関数で重要な役割の関数は次のとおりです(生成・破棄の関数は除きます)。

  • pthread_cond_signal:条件変数シグナル送信
  • pthread_cond_wait:条件変数の待ち
  • pthread_mutex_lock:ミューテックスのロック
  • pthread_mutex_unlock:ミューテックスのアンロック
  • pthread_barrier_wait:バリア同期

確認環境

シリーズ #1と同じです。
※今回はAPS学習ボードのスイッチ、LEDを使わないためSpresenseメインボードのみで確認可能です。

テストコード説明

今回のテストコードはGitHub[1]におきました。
ディレクトリ・実行ファイル名称はpthread_douzodouzoです。

次は【どうぞどうぞ】のアクティビティ図です。

どうぞどうぞのアクティビティ図

リーダー、ジモンさん、竜さんの3スレッドにしました。
条件変数、バリアを使い同期をとってアクティビティを進めていきます。

条件変数で待ち合わせ

リーダーとジモンさんの【俺がやるよ】のくだりは条件変数で待ち合わせしています。
条件変数を使った理由ですがリーダーとジモンさんの1対1の待ち合わせを表現するのに最適と考えたからです。

次はリーダーのスレッドの条件変数の待ち合わせを抽出したコードです。
自分のアクティビティが終了したらジモンさんスレッドの条件待ちを解除するためにpthread_cond_signalで条件変数解除のシグナルを送信します。
その後はジモンさんの【俺がやるよ】待ちになるのでpthread_cond_waitで条件待ちに入ります。

pthread_douzodouzo_main.c リーダーのスレッド // 該当部分を抽出し記載
void* thread_leader(void* arg) {
  enum dachoClubMember member_index = (uint32_t)arg;
  int ret;

  printf("[%s]:熱湯風呂入ったらおいしいよね。おれやるよ。\n", dachoClubTable[member_index].name);
  sleep(2);
  pthread_cond_signal(&raise_hand_furi_cond[Jimon_san]);

  // 条件待ち
  pthread_mutex_lock(&raise_hand_furi_mutex[member_index]);
  if (pthread_cond_wait(&raise_hand_furi_cond[member_index], &raise_hand_furi_mutex[member_index]) != 0) {
      printf("Fatal error on pthread[%d]_cond_wait.\n", member_index);
      exit(1);
  }
  pthread_mutex_unlock(&raise_hand_furi_mutex[member_index]);

  printf("[%s]:おれやるよ。\n", dachoClubTable[member_index].name);
  sleep(2);
  
}

次はジモンさんのスレッドです。
リーダーのスレッドと同じくpthread_cond_waitで待ち、pthread_cond_signalで条件変数解除のシグナルを送信します。

pthread_douzodouzo_main.c ジモンさんのスレッド // 該当部分を抽出し記載
void* thread_jimon_san(void* arg) {
  enum dachoClubMember member_index = (uint32_t)arg;
  int ret;

  // 条件待ち
  pthread_mutex_lock(&raise_hand_furi_mutex[member_index]);
  if (pthread_cond_wait(&raise_hand_furi_cond[member_index], &raise_hand_furi_mutex[member_index]) != 0) {
      printf("Fatal error on pthread[%d]_cond_wait.\n", member_index);
      exit(1);
  }
  pthread_mutex_unlock(&raise_hand_furi_mutex[member_index]);

  printf("[%s]:いやいや、俺がやるよ。\n", dachoClubTable[member_index].name);
  sleep(2);
  pthread_cond_signal(&raise_hand_furi_cond[Leader]);
  
}

バリアで待ち合わせ

リーダーとジモンさんの絡みが終わったら竜さんも絡んでいきます。
リーダー・ジモンさん・竜さんの3人でタイミングを合わせてアクティビティを進めるためにバリアを使って同期をとります。
バリアは次の用途で4つ使用しています。

  • raise_hand_furi_completion_barrier:リーダー・ジモンさんの【俺がやるよ】を竜さんが待つ
  • raise_hand_barrier:竜さんの【俺がやるよ】をリーダー・ジモンさんが待つ
  • douzo_wait_barrier:リーダー・ジモンさんの【どうぞどうぞ】を竜さんが待つ
  • tukkomi_wait_barrier:竜さんの【おい!!!】のツッコミをリーダー・ジモンさんが待つ

次は竜さんのスレッドのコードです。

pthread_douzodouzo_main.c 竜さんのスレッド // 該当部分を抽出し記載
void* thread_ryu_san(void* arg) {
  enum dachoClubMember member_index = (uint32_t)arg;
  int ret;

  ret = pthread_barrier_wait(&raise_hand_furi_completion_barrier);
  if (!(ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD)) {
    printf("Error; thread index[%d] pthread_barrier_wait ret = %d.\n", member_index, ret);
    exit(1);
  }

  printf("[%s]:じゃあ、俺がやるよ。\n", dachoClubTable[member_index].name);
  sleep(2);
  printf("\n");

  ret = pthread_barrier_wait(&raise_hand_barrier);
  if (!(ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD)) {
    printf("Error; thread index[%d] pthread_barrier_wait(raise_hand_barrier) ret = %d.\n", member_index, ret);
    exit(1);
  }

  ret = pthread_barrier_wait(&douzo_wait_barrier);
  if (!(ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD)) {
    printf("Error; thread index[%d] pthread_barrier_wait(douzo_wait_barrier) ret = %d.\n", member_index, ret);
    exit(1);
  }

  sleep(2);
  printf("\n");
  printf("[%s]:おい!!!\n", dachoClubTable[member_index].name);
  printf("\n");
  sleep(2);

  ret = pthread_barrier_wait(&tukkomi_wait_barrier);
  if (!(ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD)) {
    printf("Error; thread index[%d] pthread_barrier_wait(tukkomi_wait_barrier) ret = %d.\n", member_index, ret);
    exit(1);
  }

  printf("[%s]:やー!!!\n", dachoClubTable[member_index].name);

  return (void*)member_index;
}

動作確認結果

printf出力の文字情報のみだと動作がよくわからないので動画を撮りました。

https://youtu.be/V-F9rGcYXVc

プログラムを実行するとリーダー・ジモンさん・竜さんスレッドがアクティビティ図のようにどうぞどうぞのフレーズをprintf出力します。

nsh> pthread_douzodouzo
[リーダー]:熱湯風呂入ったらおいしいよね。おれやるよ。
[ジモンさん]:いやいや、俺がやるよ。
[リーダー]:おれやるよ。
[竜さん]:じゃあ、俺がやるよ。

[リーダー]:どうぞ、どうぞ
[ジモンさん]:どうぞ、どうぞ

[竜さん]:おい!!!

[竜さん]:やー!!!
[ジモンさん]:やー!!!
[リーダー]:やー!!!
Bye.

動作確認方法

シリーズ #1と同じです。
今回のアプリケーション名はpthread_douzodouzoです。

今回の感想

ダチョウ倶楽部さんの【どうぞどうぞ】をpthreadsで実装し再現することができました。
複数スレッド間で条件変数・バリアを使い同期をとる方法について理解を深めることができました。

今回も最後まで読んでいただきありがとうございました。

脚注
  1. GitHubリンク ↩︎