ESP32でマルチスレッド：セマフォを使う

![SS 192.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/a41cd967-f549-8677-1efa-2e99bf69518d.png) スマホみたいでピンと来ない。そもそも何語だ。

#やること

[前々回](https://qiita.com/Ninagawa_Izumi/items/5c3a9d40996836bd825f)、[前回](https://qiita.com/Ninagawa_Izumi/items/f75400b579ef6fbcbdf4)の続きです。ESP32DevKitCとArduinoIDEを使って、ESP32でマルチコア・マルチスレッドを使っていきます。

複数のプロセスを並行処理するマルチスレッドでは、スレッド間でデータの受け渡しをする際にルールを決めておかないと、同じデータを同時に上書きするなどの問題が生じます。
今回は**セマフォを使った制御**を試してみます。

#なんとセマフォは英単語でした
セマフォ(semaphore)とはセマンティックふぉにゃららとかの略とかではなく、一つの単語で「腕木式信号機」や手旗信号を示す言葉だそうです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/fab09a70-2360-0c03-8428-2bd46233b833.png" width = 300>
この信号機の概念でデータの交通整理をするということのようです。
たとえば銀行のデータの場合。残高100万円の口座に対し、100万円を引き落とす命令を２箇所から全く同時にドンピシャなタイミングで行うことができれば、合計200万円を引き落とせてしまうこともできそうです。
それでは困るので、複数のスレッドからアクセスしうるデータに対して、それが書き換え可能な状態かどうかをセマフォで管理するわけです。

#セマフォのイメージ

語源が信号機のセマフォですが、ふるまいについては皿の上に「ボール」が置いてあるシチュエーションの方がしっくりくるかもしれません。
ボールを手に取ることができれば、その人はボールを持っている間、データにアクセスできる権利が与えられます。
アクセスが終わったら、ボールを元の場所に戻します。それがアクセス権の放棄となります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/a858e229-4beb-4795-87ee-f40de4e86ac7.gif" width = 500>
(gifアニメです。動かない場合はクリックすると動きます。)

このボールが１個の時は、一度にアクセスできるのは１人だけ。
ボールが２個なら同時に２人、１０個あれば１０人まで同時にアクセスできるというルールになります。

#P動作とV動作
また、セマフォの説明文を読んでいて頭がいたくなるのが、P動作、V動作、wait命令、signal命令、インクリメント、デクリメントといった用語です。とくにPやVは語源がオランダ語らしく、まったくピンときません。
・P動作/wait命令はボールを取る動作。取れるまで待つ命令。皿のボールを減らすのでデクリメント。
・V動作/signal命令はボールを皿に置く動作。許可信号を出す命令。皿のボールを増やすのでインクリメント。
となります。
これを若干のこじつけで覚えてしまおうと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/97fbf90a-acb3-d222-654d-49b57977e25e.png" width="350">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/687b1109-4817-c713-fa30-9abdbf4d53c1.png" width="350">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/f205cf83-ad87-9bf1-2532-2c2765370c8f.png" width="350">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/d685d0b5-c371-6af8-03da-48b5fb98739b.png" width="350">

Vacateは放棄という意味ですが、バケーションの語源と一緒です。仕事を放棄しバケーションです。

**ここでp,v,wait,signalといった用語の機能を把握しておくだけで、ネット上のFreeRTOSや排他制御の解説やマニュアルがぐっと読みやすくなる**と思います。

#セマフォのテストスクリプト

つぎに、ESP32DevKitCとArduinoIDEでセマフォをスクリプトにしてみます。
####準備物
- Arduino IDEの入ったPC（ESP32ボードが使えるようにしてある）
- ESP32DevKitCなど

```c++:SemaphoreTest.ino
SemaphoreHandle_t xBinarySemaphore;//セマフォの準備（型宣言とセマフォハンドル名）
TaskHandle_t thp[2];//タスクハンドルのポインタ

void setup()
{
  Serial.begin(115200);
  xBinarySemaphore = xSemaphoreCreateBinary();//セマフォハンドルを作成
  //コア0を指定してタスク(スレッド)を作成
  xTaskCreatePinnedToCore(Mario, "Mario", 4096, NULL, 2, &thp[0], 0);
  //コア1を指定してタスク(スレッド)を作成
  xTaskCreatePinnedToCore(Luige, "Luige", 4096, NULL, 1, &thp[1], 1);
  //xTaskCreatePinnedToCore( //コアを指定してタスクを作成するコマンド
  //   [タスク名], "[タスク名]",
  //   [スタックメモリサイズ(4096or8192)], [NULL],
  //   [タスク優先順位(1-24)] 大きいほど優先順位が高い,
  //   [宣言したタスクハンドルのポインタ(&thp[0])], [CoreID(0or1)]);
}

void Mario (void *args)//まりお役のスレッド
{
  bool xSemaTama;//セマフォ値を受け取るためのローカル変数
  Serial.println("Mario Start.");
  while (1)
  {
    //セマフォ値を受け取る
    xSemaTama = xSemaphoreTake(xBinarySemaphore, 2000);//★
    //xSemaphoreTake([セマフォハンドル名], [ウェイトタイム]);
    if (xSemaTama == pdTRUE)//セマフォ権をゲットできたかをチェック
                            //pdTRUEはセマフォ判定専用のTrue
    {
      Serial.println("Mario: take");
      //xSemaphoreGive(xBinarySemaphore);//★セマフォをリリースする命令
    }
    else {
      Serial.println("Mario: wait");
    }
    delay(1);
  }
}

void Luige (void *args)//るいじ役のスレッド
{
  bool xSemaTama;
  Serial.println("Luige Start.");
  while (1)
  {
    xSemaTama = xSemaphoreTake(xBinarySemaphore, 2000);//★
    if (xSemaTama == pdTRUE)
    {
      Serial.println("Luige: take");
      //xSemaphoreGive(xBinarySemaphore);//★セマフォをリリースする命令
    }
    else {
      Serial.println("Luige: wait");
    }
    delay(1);
  }
}

void loop()//メインループ（これもスレッド）
{
  int xSemaTama = pdFALSE;
  //このスレッドから他スレッドが持っているセマフォを強制リリースさせる
  xSemaphoreGiveFromISR(xBinarySemaphore, &xSemaTama);//★
  Serial.println("mainloop");
  delay(2000);//★メインループの待ち時間
}
```
FreeRTOSには変数名の冒頭にp,v,s,xなどをつけるルールがあるようですが、そのあたりは一旦無視し、なるべく見慣れた変数などを使うようにしています。


#実行
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/f07f8127-7977-eb0e-874f-6b6e7481dee1.png" width=100>
実行するとシリアルモニタ上でMarioとLuigiが玉をを奪い合います。
上記スクリプトではマリオの優先順位が高くなるように設定しており、ディレイ時間が同じであるため、常にMarioがボールをとる形になります。
★印のところのセマフォ待ちのタイムアウト時間を変更したり、セマフォをリリースする行をアンコメントやコメントアウトするなどし、各挙動をチェックしてみてください。

# ポイント
・xSemaphoreTake([セマフォハンドル名], [ウェイトタイム])のウェイトタイムはミリ秒単位です。
・xSemaphoreGiveFromISR()は他のスレッドが持っているセマフォ権をリリースさせることができます。
・セマフォはその仕組みを活用してスレッド同士の同期のために使われることが多いようです。
・今回はセマフォを玉１個（バイナリ）で作成しましたが、これは「ミューテックス」と呼ばれるものとほぼ同じ働きになるようです。
・ミューテックスは排他制御（同時アクセスの回避）に特化したコマンドのようです。
・メインループもスレッドのひとつとなります。メインはCore1で動いています。メインの優先度値は0のようです。

#間違いをご指摘ください
調べながら書いておりますので、変なところがありましたら何卒ご指摘ください。

#参考URL
[FreeRTOSでマルチタスク (on ESP32)](https://qiita.com/hideakitai/items/bd95d0db63097b8808f9)
[「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word13357.html)
[FreeRTOS の概要（自動翻訳 無保証）](http://www.azusa-st.com/kjm/FreeRtos/FreeRTOS.html)