やること
前々回、前回の続きです。ESP32DevKitCとArduinoIDEを使って、ESP32でマルチコア・マルチスレッドを使っていきます。
複数のプロセスを並行処理するマルチスレッドでは、スレッド間でデータの受け渡しをする際にルールを決めておかないと、同じデータを同時に上書きするなどの問題が生じます。
今回はセマフォを使った制御を試してみます。
なんとセマフォは英単語でした
セマフォ(semaphore)とはセマンティックふぉにゃららとかの略とかではなく、一つの単語で「腕木式信号機」や手旗信号を示す言葉だそうです。

セマフォのイメージ
語源が信号機のセマフォですが、ふるまいについては皿の上に「ボール」が置いてあるシチュエーションの方がしっくりくるかもしれません。
ボールを手に取ることができれば、その人はボールを持っている間、データにアクセスできる権利が与えられます。
アクセスが終わったら、ボールを元の場所に戻します。それがアクセス権の放棄となります。
(gifアニメです。動かない場合はクリックすると動きます。)
このボールが1個の時は、一度にアクセスできるのは1人だけ。
ボールが2個なら同時に2人、10個あれば10人まで同時にアクセスできるというルールになります。
P動作とV動作
また、セマフォの説明文を読んでいて頭がいたくなるのが、P動作、V動作、wait命令、signal命令、インクリメント、デクリメントといった用語です。とくにPやVは語源がオランダ語らしく、まったくピンときません。
・P動作/wait命令はボールを取る動作。取れるまで待つ命令。皿のボールを減らすのでデクリメント。
・V動作/signal命令はボールを皿に置く動作。許可信号を出す命令。皿のボールを増やすのでインクリメント。
となります。
これを若干のこじつけで覚えてしまおうと思います。




Vacateは放棄という意味ですが、バケーションの語源と一緒です。仕事を放棄しバケーションです。
ここでp,v,wait,signalといった用語の機能を把握しておくだけで、ネット上のFreeRTOSや排他制御の解説やマニュアルがぐっと読みやすくなると思います。
セマフォのテストスクリプト
つぎに、ESP32DevKitCとArduinoIDEでセマフォをスクリプトにしてみます。
準備物
- Arduino IDEの入ったPC(ESP32ボードが使えるようにしてある)
- ESP32DevKitCなど
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などをつけるルールがあるようですが、そのあたりは一旦無視し、なるべく見慣れた変数などを使うようにしています。
実行

ポイント
・xSemaphoreTake([セマフォハンドル名], [ウェイトタイム])のウェイトタイムはミリ秒単位です。
・xSemaphoreGiveFromISR()は他のスレッドが持っているセマフォ権をリリースさせることができます。
・セマフォはその仕組みを活用してスレッド同士の同期のために使われることが多いようです。
・今回はセマフォを玉1個(バイナリ)で作成しましたが、これは「ミューテックス」と呼ばれるものとほぼ同じ働きになるようです。
・ミューテックスは排他制御(同時アクセスの回避)に特化したコマンドのようです。
・メインループもスレッドのひとつとなります。メインはCore1で動いています。メインの優先度値は0のようです。
間違いをご指摘ください
調べながら書いておりますので、変なところがありましたら何卒ご指摘ください。
参考URL
FreeRTOSでマルチタスク (on ESP32)
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
FreeRTOS の概要(自動翻訳 無保証)