🐯

SpresenseでNuttXのpthreadsを使用しマルチスレッドを学ぶ #3 〜条件変数で条件待ちする〜

2022/03/24に公開

これは何?

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

今回のテーマ

今回は条件変数を使ってプログラムの実行を制御します。
今回使うpthreadsの関数は次のとおりです。

  • pthread_cond_init:条件変数の初期化
  • pthread_cond_wait:条件待ち
  • pthread_cond_signal:条件待ち解除
  • pthread_cond_destroy:条件変数の削除

確認環境

シリーズ #1と同じです。

テストコード説明

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

条件変数で待つスレッドは2つで関数名はthread_cond_wait_funcです。
条件待ち解除するスレッドも2つで関数名はthread_cond_signal_funcです。
APS学習ボードのスイッチを押下するとthread_cond_signal_funcで条件待ち解除のpthread_cond_signalを呼び出します。
thread_cond_wait_funcは条件待ちしており、条件解除されたらスレッドを終了します。
thread_cond_wait_func・thread_cond_signal_funcで条件待ち・条件待ち解除に使う条件変数はスレッドの引数で決めています。

条件変数の定義

条件変数を定義します。

pthread_cond_main.c // 該当部分を抽出し記載
#define THREAD_NUM (2)  // スレッドの数

pthread_cond_t switch_cond[THREAD_NUM];

条件変数の初期化

条件変数を利用するにはpthread_cond_initで初期化します。

pthread_cond_main.c main関数 // 該当部分を抽出し記載
int main(int argc, FAR char *argv[])
{
  pthread_t thread_cond_signal[THREAD_NUM];
  pthread_t thread_cond_wait[THREAD_NUM];
  int ret[THREAD_NUM];
  int i;

  for(i = 0; i < THREAD_NUM; i++){
    if (pthread_cond_init(&switch_cond[i], NULL) != 0) {
        printf("Error; pthread_cond_init-%d.\n", i);
        exit(1);
    }
  }

}

条件待ち

条件待ちはpthread_cond_waitで実現します。
ミューテックスロック・アンロックでthread_cond_waitを括ること、thread_cond_waitの引数にミューテックスを指定しています。

pthread_cond_main.c thread_cond_wait_func関数 // 該当部分を抽出し記載
void* thread_cond_wait_func(void* arg) {
  int switch_index = (uint32_t)arg;

  printf("thread_cond_wait[%d]:cond_wait[%d]...\n", switch_index, switch_index);

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

  printf("thread_cond_wait[%d]:wait end.\n", switch_index);

  return (void*)switch_index;
}

条件待ち解除

条件待ち解除はpthread_cond_signalで実現します。
APS学習ボードのスイッチ1または2が押下されたらpthread_cond_signalを実行し、条件待ち解除します。

pthread_cond_main.c thread_cond_signal_func関数 // 該当部分を抽出し記載
void* thread_cond_signal_func(void* arg) {
  int switch_index = (uint32_t)arg;

  volatile int switch_status = board_gpio_read(aps_board_switch_pin_3[switch_index]);

  while (switch_status == 1) {  // スイッチ押下されたらループから抜ける
    switch_status = board_gpio_read(aps_board_switch_pin_3[switch_index]);
    sleep(1);
  }

  pthread_cond_signal(&switch_cond[switch_index]);

  printf("thread_cond_signal[%d]:switch[%d] pushed!!!cond_signal[%d] set.\n", switch_index, switch_index + 1, switch_index);

  return (void*)switch_index;
}

条件変数の削除

条件変数が不要になったらpthread_cond_destroyで削除します。

pthread_cond_main.c main関数 // 該当部分を抽出し記載
#define THREAD_NUM (2)  // スレッドの数

pthread_cond_t switch_cond[THREAD_NUM];

int main(int argc, FAR char *argv[])
{
  pthread_t thread_cond_signal[THREAD_NUM];
  pthread_t thread_cond_wait[THREAD_NUM];
  int ret[THREAD_NUM];
  int i;

  for (i = 0; i < THREAD_NUM; i++) {
    if (pthread_cond_destroy(&switch_cond[i]) != 0) {
        printf("Error; pthread_cond_destroy-%d.\n", i);
        exit(1);
    }
  }

  printf("Bye.\n");

  return 0;
}

動作確認結果

プログラムを実行するとthread_cond_waitは待ち状態になります。
APS学習ボードのスイッチ1または2を押下するとthread_cond_signalが条件待ち解除します。
thread_cond_waitは条件待ち解除のシグナルを受け取りスレッドを終了しています。
thread_cond_wait[0]はthread_cond_signal[0]、thread_cond_wait[1]はthread_cond_signal[1]の条件変数解除を待ちます。
次はAPS学習ボードのスイッチ1→スイッチ2の順番にスイッチを押下した場合のprintfです。

nsh> pthread_cond
thread_cond_wait[0]:cond_wait[0]...
thread_cond_wait[1]:cond_wait[1]...
thread_cond_signal[0]:switch[1] pushed!!!cond_signal[0] set.
thread_cond_wait[0]:wait end.
main:thread_cond_signal[0] exit.return value = 0
thread_cond_signal[1]:switch[2] pushed!!!cond_signal[1] set.
thread_cond_wait[1]:wait end.
main:thread_cond_signal[1] exit.return value = 1
main:thread_cond_wait[0] exit.return value = 0
main:thread_cond_wait[1] exit.return value = 1
Bye. 

次はAPS学習ボードのスイッチ2→スイッチ1の順番にスイッチを押下した場合のprintfです。
main関数はthread_cond_signal[0]の終了を待つようにしているのでスイッチ1→スイッチ2の時とprintfの表示順番が違っています。

nsh> pthread_cond
thread_cond_wait[0]:cond_wait[0]...
thread_cond_wait[1]:cond_wait[1]...
thread_cond_signal[1]:switch[2] pushed!!!cond_signal[1] set.
thread_cond_wait[1]:wait end.
thread_cond_signal[0]:switch[1] pushed!!!cond_signal[0] set.
thread_cond_wait[0]:wait end.
main:thread_cond_signal[0] exit.return value = 0
main:thread_cond_signal[1] exit.return value = 1
main:thread_cond_wait[0] exit.return value = 0
main:thread_cond_wait[1] exit.return value = 1
Bye.

動作確認方法

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

今回の感想

条件変数で待ち状態になり、条件成立で待ち状態を解除することを確認できました。
Spresense・NuttXだからといって特別なことはなく、ホストPCと同じようにpthreadsの条件変数を実現できました。
今回は永久に条件待ちするコードでした。次回はタイムアウト付き条件待ち(pthread_cond_timedwait)を試してみようと思います。

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

脚注
  1. GitHubリンク ↩︎

Discussion