[ESP32]セマフォを使用する方法(xSemaphore)

ESP32

Webサーバーを構築する方法

はじめに

ESP32でセマフォを使用する一例をご紹介いたします。

セマフォとは

セマフォは、ある資源が何個使用可能かを示す記録と考えればわかりやすく、それにその資源を使用する際や解放する際にその記録を「安全に」(すなわち競合状態となることなく)書き換え、必要に応じて資源が使用可能になるまで待つ操作が結びついている。

https://ja.wikipedia.org/wiki/%E3%82%BB%E3%83%9E%E3%83%95%E3%82%A9

並列して動作する複数のタスクが、同じ領域を変更する際に、変更途中に別タスクが書き込むと意図しない値になることがあります。一度経験するとわかりやすく、どのような場所に必要なのかも目星がつきやすくなります。

en:semaphore」の本来の語義は「視覚による通信信号」全般を指し、腕木通信や、それから派生した鉄道腕木信号(や自動車方向指示器)、手旗信号などが含まれる。

https://ja.wikipedia.org/wiki/%E3%82%BB%E3%83%9E%E3%83%95%E3%82%A9

押しボタン式信号機のようなもので、交差点内でぶつからないようにするものと考えると少しわかりやすいかもしれません。ただし、押しボタン式信号機のように青にして通行したあと、時間経過すると別車線側が青になるような制御がないので、自分が通った後の処理をしないと渋滞してしまいます。(デッドロックですね。)

開発環境

OS : Windows 11 Pro

ESP32:ESP-WROOM-32

統合開発環境 : Arduino IDE 2.1.0

使用ライブラリ:なし

作業内容

使用関数の把握

xSemaphoreCreateBinary()
バイナリセマフォ作成。

引数説明
なし
戻り値作成したセマフォへのハンドル。失敗時はNULL。

バイナリセマフォ作成後は、xSemaphoreGive() で解放しておかないと xSemaphoreTake() で取得しようとしても失敗します。

whereas binary semaphores created using xSemaphoreCreateBinary() are created in a state such that the the semaphore must first be ‘given’ before it can be ‘taken’.

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos_idf.html?highlight=xqueuecreate#c.xSemaphoreCreateBinary:~:text=semaphore%20would%20pass%2C-,whereas%20binary%20semaphores%20created%20using%20xSemaphoreCreateBinary()%20are%20created%20in%20a%20state%20such%20that%20the%20the%20semaphore%20must%20first%20be%20%E2%80%98given%E2%80%99%20before%20it%20can%20be%20%E2%80%98taken%E2%80%99.,-This%20type%20of

xSemaphoreTake()
セマフォを取得するマクロ。

引数説明
xSemaphore 取得するセマフォへのハンドル
xBlockTime セマフォが使用可能になるまで待機する時間 (ティック単位)。
戻り値セマフォが取得された場合は pdTRUE。失敗時は pdFALSE。

xSemaphoreGive()
セマフォを解放するマクロ。

引数説明
xSemaphore 解放するセマフォへのハンドル。
戻り値セマフォが解放された場合は pdTRUE。失敗時は pdFALSE。

スケッチ作成

「xSemaphore_Sample」というフォルダを作り、「xSemaphore_Sample.ino」というファイルを作成して、下記スケッチを作成しています。

配置例

「C:\Users\xxxxx\OneDrive\ドキュメント\Arduino\xSemaphore_Sample

<<xSemaphore_Sample.ino>>

TaskHandle_t thp[2];
SemaphoreHandle_t xSemaphore;

// the setup function runs once when you press reset or power the board
void setup() {
  
  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);
  
  // セマフォ作成
  xSemaphore = xSemaphoreCreateBinary();
  if(xSemaphore == NULL) 
  {
    Serial.println("セマフォ取得失敗");
  }
  else
  {
    xSemaphoreGive( xSemaphore );
  }

  // Now set up two tasks to run independently.
  xTaskCreatePinnedToCore( TaskA, "TaskA", 2048, NULL, 2, &thp[0], APP_CPU_NUM);
  xTaskCreatePinnedToCore( TaskB, "TaskB", 2048, NULL, 2, &thp[1], APP_CPU_NUM);
}

void loop()
{
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
void TaskA(void *pvParameters)  // This is a task.
{
  Serial.println("TaskA Start");
  while( 1 ) {
    // セマフォ取得
    if( xSemaphoreTake( xSemaphore, 0 ) == pdTRUE )
    {
      Serial.println("TaskA セマフォ取得成功");
      delay(5000);
      // セマフォ解放
      xSemaphoreGive( xSemaphore );
      delay(2000);
    }
    else
    {
      Serial.println("TaskA セマフォ取得待ち");
      delay(1000);
    }
  }
  // ここにはこないが明示的にタスク終了を記載しておく。
  vTaskDelete(NULL);
}

void TaskB(void *pvParameters)  // This is a task.
{
  Serial.println("TaskB Start");
  while( 1 ) {
    // セマフォ取得
    if( xSemaphoreTake( xSemaphore, 0 ) == pdTRUE )
    {
      Serial.println("TaskB セマフォ取得成功");
      delay(5000);
      // セマフォ解放
      xSemaphoreGive( xSemaphore );
      delay(2000);
    }
    else
    {
      Serial.println("TaskB セマフォ取得待ち");
      delay(1000);
    }
  }
  // ここにはこないが明示的にタスク終了を記載しておく。
  vTaskDelete(NULL);
}

TaskAとTaskBを作成し、2タスクともに以下のような処理をしています。

各 Task 処理内容

セマフォを獲得 → 成功すると5秒待ってセマフォ解放して2秒待つ。
          (2秒の間に別タスクにセマフォを獲得させる。)
        → 失敗すると1秒待って再トライ。

動作確認

セマフォを取得した Task が5秒間取得したままとなるため、セマフォを取得できなかった Task は1秒毎に取得しようと試みます。5秒後に取得していた Task がセマフォを解放すると、もう片方の Task がセマフォ取得に成功します。

TaskA と TaskB が交互にセマフォを取得していますね。

xSemaphoreCreateBinary() した後に、xSemaphoreGive() をしておかないと失敗するので要注意です。

おわりに

セマフォの使用方法がわかりました。複数タスクが同じ領域を参照や書き換えをする場所には、安全に書き換えできるようセマフォを活用できます。

現在私が作成しているシステムでは、複数タスクがログを蓄積するリングバッファ領域を書き換える処理があります。書き換え処理の前にセマフォを取得し、書き換えた後に開放し、排他制御をしています。

コメント

タイトルとURLをコピーしました