1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【FreeRTOS × ESP32】Semaphoreとは?排他制御とMutexの必要性を初心者向けに解説

Last updated at Posted at 2025-12-10

この記事でわかること

  • セマフォ(Semaphore)とは何か
  • セマフォを使わないと何が起きるか
  • セマフォを使うメリット
  • Binary Semaphore と Mutex の違い

この記事に掲載しているコードは Arduino for ESP32 + FreeRTOS でそのまま動作します。タスクAとタスクBを同時に動かし、UARTログ混在 → セマフォ改善を体験できます。

目次

  1. FreeRTOS Semaphore(セマフォ)とは?役割と用途
  2. なぜ FreeRTOS でセマフォが必要か?(排他制御の重要性)
  3. セマフォ未使用の実装例
  4. Binary Semaphoreで改善する
  5. FreeRTOS Binary Semaphore の限界
  6. 「優先度逆転」という現象について
  7. FreeRTOS Mutexが優先度逆転を解決する理由
  8. FreeRTOSで Binary Semaphore と Mutex を使い分ける基準
  9. まとめ

1. FreeRTOS Semaphore(セマフォ)とは?役割と用途

FreeRTOSの Semaphore(セマフォ) は、複数タスクが1つの共有リソースを同時に使わないようにする仕組みです。

共有リソースとは…

  • UART(シリアル出力)
  • I2Cセンサー
  • SPIバス
  • SDカード書き込み
  • WiFi送信処理
  • 共有メモリ
    など、同時アクセスすると壊れる可能性があるものです。

2. なぜ FreeRTOS でセマフォが必要か?(排他制御の重要性)

複数タスクが同時に Serial.print() などを実行すると、ログが混在したりデータが破損します。
そのため、リソース数を制限しデータの破損を防ぐ必要があります。

3. セマフォ未使用の実装例

Serial.printでタスクA・Bに以下のような表示をするよう実装します。
タスクA:[A] START END
タスクB:[B] BEGIN DONE

まずは、セマフォを使わずに実装してみます。

#include <Arduino.h>

// セマフォなし

void TaskA(void *pvParameters) {
  for (;;) {
    Serial.print("[A] START ");
    delayMicroseconds(random(500, 2000));
    Serial.println(" END");
    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

void TaskB(void *pvParameters) {
  for (;;) {
    Serial.print("[B] BEGIN ");
    delayMicroseconds(random(500, 2000));
    Serial.println(" DONE");
    vTaskDelay(pdMS_TO_TICKS(300));
  }
}

void setup() {
  Serial.begin(115200);
  randomSeed(esp_random());

  xTaskCreate(TaskA, "TaskA", 2048, NULL, 1, NULL);
  xTaskCreate(TaskB, "TaskB", 2048, NULL, 1, NULL);
}

void loop() {
  vTaskDelay(portMAX_DELAY);
}

以下実際のログになります。
タスクAとタスクBのログが混ざっており、正しく表示されていないことがわかります。
image.png

今は正しく表示されないだけで済みますが、実際に通信やログが正しく機能してくれないとなるとIoTとして致命的です。

4. Binary Semaphoreで改善する

それではFreeRTOSのセマフォを使用し、どちらかのタスクがSerial.printを行う際は、もう片方タスクはSerial.printを行わないように修正します。
以下、修正コードになります。

#include <Arduino.h>

SemaphoreHandle_t uartLock;

void TaskA(void *pvParameters) {
  for (;;) {
    if (xSemaphoreTake(uartLock, portMAX_DELAY) == pdTRUE) {
      Serial.print("[A] START ");
      delayMicroseconds(random(500, 2000));
      Serial.println(" END");
      xSemaphoreGive(uartLock);
    }
    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

void TaskB(void *pvParameters) {
  for (;;) {
    if (xSemaphoreTake(uartLock, portMAX_DELAY) == pdTRUE) {
      Serial.print("[B] BEGIN ");
      delayMicroseconds(random(500, 2000));
      Serial.println(" DONE");
      xSemaphoreGive(uartLock);
    }
    vTaskDelay(pdMS_TO_TICKS(300));
  }
}

void setup() {
  Serial.begin(115200);
  randomSeed(esp_random());

  uartLock = xSemaphoreCreateBinary();
  xSemaphoreGive(uartLock);

  xTaskCreate(TaskA, "TaskA", 2048, NULL, 1, NULL);
  xTaskCreate(TaskB, "TaskB", 2048, NULL, 1, NULL);
}

void loop() {
  vTaskDelay(portMAX_DELAY);
}

それでは、修正された実装のログはどうでしょうか
image.png
タスクAとタスクBのログが混ざらず、正しく表示されていることがわかります。
これが「排他制御」になります。

5. FreeRTOS Binary Semaphore の限界

Binary Semaphoreは

  • 軽い
  • シンプル
  • ISR通知にも使える

…というメリットがありますが、

ある問題を解決できません。

6. 「優先度逆転」という現象について

FreeRTOSはタスクごとに優先度を持っています。

本来なら、

高優先度タスク(H)
↓
中優先度タスク(M)
↓
低優先度タスク(L)

の順で実行されるべきです。
しかし Binary Semaphoreだけを使うと、こういう問題が起きます:

低優先度タスク L がリソースを使っている(ロック保持中)
↓
高優先度タスク H が「使いたい!」と待つ
↓
その間に中優先度タスク M がCPUを独占
↓
H はずっと待たされる……

結果的に低優先度タスク L がリソースを独占することで、
高優先度タスク H が実行できなくなってしまいます。
これが 優先度逆転(Priority Inversion)

優先度逆転が起きると、リアルタイム性が重要な IoT(MQTT送信・ログ収集・センサー処理など)が遅延し、結果としてデータ欠損や通信タイムアウトにつながることがあります。

7. FreeRTOS Mutexが優先度逆転を解決する理由

Mutexは Binary Semaphore で発生する優先度逆転を解決できます。

低優先度 L が鍵を保持中
↓
高優先度 H が待っている

このとき MutexはL の優先度を一時的に H と同じレベルに引き上げます。

  • MよりもLが優先されるためすぐ処理を終わらせる
  • セマフォのロックが解除される
  • H が実行できる

優先度違いのタスク間で同じセマフォのリソースを使用する場合はMutexが有効であることが、なんとなくイメージできたと思います。

8. FreeRTOSで Binary Semaphore と Mutex を使い分ける基準

軽くて短い処理

Binary Semaphore
例:

  • UARTの1行出力
  • I2Cの単発読み取り
  • ISR通知

優先度が絡む、長い処理、安全性が重要

Mutex
例:

  • データ構造保護
  • SDカード書き込み
  • 長時間ロックする処理
  • WiFi/MQTT送信
  • ファイルシステム

9. まとめ

今回は FreeRTOS の Semaphore(セマフォ) について紹介しました。

Semaphoreは排他制御の仕組み

  • UART・I2C・SDカードなど共有リソースを守る
  • 同時アクセスによるデータ破損を防ぐ

Binary Semaphoreは軽量で簡単

  • 短い排他処理、ISR通知に向いている

Mutexは優先度逆転を防げる

  • “所有者”の概念を持ち、低優先度タスクがロックしていても高優先度を守る
  • リアルタイム性が必要なIoTに最適

使い分けの基準

  • 短時間排他 → Binary Semaphore
  • 優先度・長時間処理 → Mutex
  • SDカード、データ構造、ネットワーク通信は Mutex推奨

今後も FreeRTOS の機能について解説していきます。
次は、EventGroup に関して、実装を交えて解説したいと思います。

FreeRTOS × ESP32 記事シリーズ

ESP-IDFで動くコードやセマフォの関数の使い方はこちらに載せていますので、良ければご覧ください

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?