top of page
สนธยา นงนุช

การใช้ Mutex ใน FreeRTOS


Mutex (Mutually exclusive) ใช้ป้องกันการเข้าใช้ทรัพยากรเดียวกันในเวลาเดียวกันจากหลาย Task โดยล็อกโค้ดส่วนที่ต้องการป้องกันด้วยคำสั่ง Take และ Give

แนวคิดของ FreeRTOS มองว่า Mutex คือการสร้างกุญแจขึ้นมา 1 ดอก เมื่อ Task ใด ๆ ต้องการเข้าถึงทรัพยากร จะต้องนำกุญแจออกไป เมื่อทำงานเสร็จจึงนำกุญแจมาคืน โดยการนำกุญแจออกไปจะเรียกว่าการ Take ส่วนการคืนกุญแจจะเรียกว่าการ Give โดยทุกครั้งที่มีการ Take เมื่อโค้ดโปรแกรมส่วนนั้นทำงานเสร็จจะต้องมีการ Give เสมอ เพื่อเปิดโอกาศให้ Task อื่น ๆ ได้เข้ามาใช้ทรัพยากรต่อไป

การ Take, Give ในบางแหล่งข้อมูลจะใช้คำว่า Lock และ Unlock แทน ตัวอย่างรูปด้านแสดงการใช้ Mutex เพื่อล็อกการใช้งานทรัพพยากรกลาง (Mutex) จาก Task A (Thread A) และ Task B (Thread B)

ตัวอย่างแนวคิดเรื่องกุญแจแสดงด้านล่าง โดยเว็บไซต์ต้นทางได้อธิบายรูปภาพไว้ดังนี้

มีห้องน้ำอยู่ 1 ห้อง การเข้าห้องน้ำต้องใช้กุญแจ แต่มี 3 คนรอใช้ห้องน้ำอยู่ การเข้าห้องน้ำจะเข้าได้ครั้งละ 1 คนเท่านั้น เมื่อคนแรกออกมาจากห้องน้ำแล้ว จึงจะส่งต่อกุญแจให้คนถัดไป

บทความนี้ใช้ ESP32 กับแพลตฟอร์ม Arduino ในการทดสอบ

ทดสอบโค้ดโปรแกรมทำงานซ้อนกันเพราะไม่ใช้ Mutex

ตัวอย่างโค้ดโปรแกรมด้านล่างนี้สร้าง Task A, Task B ขึ้นมา แล้วเรียกใช้ฟังก์ชั่น print_number(); จาก Task A, Task B แต่ใน Task B มีการหน่วงเวลาด้วยคำสั่ง delay(); ให้เกิดการ Overlab ของเวลาเล็กน้อย

#include <Arduino.h>

void print_number() { // ฟังก์ชั่นให้ print 0 1 ... 9 ออกทาง Serial Monitor
  for (int i=0;i<10;i++) {
    Serial.printf("%d\t", i);
    delay(10);
  }
  Serial.println();
}

void task_a(void*) { // สร้างฟังก์ชั่น Task A
  print_number(); // เรียกใช้ฟังก์ชั่นแสดงเลข 0 1 2 ... 9

  vTaskDelete(NULL);
}

void task_b(void*) { // สร้างฟังก์ชั่น Task B
  delay(30); // หน่วงเวลา 30 วินาที เพื่อให้เกิดการ Overlab
  print_number(); // เรียกใช้ฟังก์ชั่นแสดงเลข 0 1 2 ... 9

  vTaskDelete(NULL);
}

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

  xTaskCreate(task_a, "task_a", 2 * 1024, NULL, 20, NULL); // สร้าง Task A
  xTaskCreate(task_b, "task_b", 2 * 1024, NULL, 20, NULL); // สร้าง Task B
}

void loop() {
  delay(500);
}

ความคาดหวังคือตัวเลขควรแสดงออกมา 0 1 2 3 .... 9 ไปเรื่อย ๆ แต่ผลที่ได้คือ 0 1 2 0 3 1 4 ... 9 7 8 9 จะเห็นว่าตัวเลขที่แสดงออกมาไม่เรียงกัน เพราะเลข 0 1 2 ชุดแรกเกิดจาก Task A แต่เลขที่แทรกมา เกิดจาก Task B

ปรับโค้ดโปรแกรมใหม่โดยใส่ Mutex เข้าไปล็อกการทำงาน ได้โค้ดโปรแกรมใหม่ ดังนี้

#include <Arduino.h>

QueueHandle_t xPrintNumberMutex; // สร้างตัวแปรเก็บ Handle ของ Mutex

void print_number() { // ฟังก์ชั่นให้ print 0 1 ... 9 ออกทาง Serial Monitor
  if (xSemaphoreTake(xPrintNumberMutex, pdMS_TO_TICKS(1000)) == pdFALSE) { // Take Mutex
    Serial.println("Take mutex fail"); // แสดงข้อความ "Take mutex fail" หาก Take ไม่สำเร็จ
    return;
  }

  for (int i=0;i<10;i++) {
    Serial.printf("%d\t", i);
    delay(10);
  }
  Serial.println();

  xSemaphoreGive(xPrintNumberMutex); // Give Mutex
}

void task_a(void*) { // สร้างฟังก์ชั่น Task A
  print_number(); // เรียกใช้ฟังก์ชั่นแสดงเลข 0 1 2 ... 9

  vTaskDelete(NULL);
}

void task_b(void*) { // สร้างฟังก์ชั่น Task B
  delay(30); // หน่วงเวลา 30 วินาที เพื่อให้เกิดการ Overlab
  print_number(); // เรียกใช้ฟังก์ชั่นแสดงเลข 0 1 2 ... 9

  vTaskDelete(NULL);
}

void setup() {
  Serial.begin(115200);
  xPrintNumberMutex = xSemaphoreCreateMutex(); // สร้าง Mutex เก็บ Handle ลงตัวแปร xPrintNumberMutex

  xTaskCreate(task_a, "task_a", 2 * 1024, NULL, 20, NULL); // สร้าง Task A
  xTaskCreate(task_b, "task_b", 2 * 1024, NULL, 20, NULL); // สร้าง Task B
}

void loop() {
  delay(500);
}

ผลที่ได้เป็นไปตามที่คาดหวัง คือแสดงเลข 0 ... 9 เรียงลำดับถูกต้อง จำนวน 2 ชุด โดยชุดแรกมาจาก Task A และชุดที่ 2 มาจาก Task B

การสร้าง Mutex

ใช้คำสั่ง xSemaphoreCreateMutex() สร้าง Mutex โดยใช้แรมที่เหลือ ณ ขณะนั้น หรือใช้คำสั่ง xSemaphoreCreateMutexStatic() สร้างโดยใช้แรมที่ขอไว้ล่วงหน้า

SemaphoreHandle_t xSemaphoreCreateMutex();
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);

พารามิเตอร์: ไม่มี

ค่าที่ส่งกลับ: ข้อมูลชนิด SemaphoreHandle_t โดย

  • หากสร้าง Mutex สำเร็จ - ส่งกลับ handle ของ Mutex นี้ จำเป็นต้องใช้ในคำสั่ง Take หรือ Give ต่อไป

  • หากไม่สำเร็จ (แรม ณ ขณะนั้นเหลือไม่พอให้สร้าง Mutex) - ส่งกลับ NULL

ตัวอย่างการสร้าง Mutex มีดังนี้

#include <Arduino.h>

QueueHandle_t xPrintNumberMutex; // สร้างตัวแปรเก็บ Handle ของ Mutex

void setup() {
  xPrintNumberMutex = xSemaphoreCreateMutex(); // สร้าง Mutex
}

การ Take Mutex

ใช้คำสั่ง xSemaphoreTake() กรณี Take Mutex นอกฟังก์ชั่นอินเตอร์รัพท์ หรือ xSemaphoreTakeFromISR() กรณีต้องการ Take Mutex จากในฟังก์ชั่นอินเตอร์รัพท์

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken)

พารามิเตอร์:

  • (SemaphoreHandle_t) xSemaphore - handle ของ Mutex ที่ต้องการจะ Take ค่า

  • (TickType_t) xTicksToWait - ระยะเวลานานที่สุดที่จะรอ Mutex ถูก Give ในหน่วย Tick

ค่าที่ส่งกลับ: (BaseType_t) สถานะของการ Take Mutex โดย

  • pdTRUE - สามารถ Task Mutex ได้สำเร็จ ให้เข้าใช้งานโค้ดโปรแกรมในบรรทัดต่อไปได้

  • pdFALSE - ไม่สามารถ Task Mutex ได้ภายในเวลาที่กำหนด (Timeout) ต้องห้ามเข้าใช้โค้ดที่ล็อกไว้

ตัวอย่างการ Task Mutex มีดังนี้

if (xSemaphoreTake(xPrintNumberMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { // Take Mutex
  ...; // โค้ดโปรแกรมส่วนที่ต้องการล็อกการทำงาน
  xSemaphoreGive(xPrintNumberMutex); // Give Mutex
}

การ Give Mutex

ใช้คำสั่ง xSemaphoreGive() กรณี Give Mutex นอกฟังก์ชั่นอินเตอร์รัพท์ หรือ xSemaphoreGiveFromISR() กรณีต้องการ Give Mutex จากในฟังก์ชั่นอินเตอร์รัพท์

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken) ;

พารามิเตอร์:

  • (SemaphoreHandle_t) xSemaphore - handle ของ Mutex ที่ต้องการจะ Give ค่า

ค่าที่ส่งกลับ: (BaseType_t) สถานะของการ Take Mutex โดย

  • pdTRUE - สามารถ Give Mutex ได้สำเร็จ

  • errQUEUE_FULL - ไม่สามารถ Give Mutex ได้

ตัวอย่างการ Give Mutex มีดังนี้

if (xSemaphoreTake(xPrintNumberMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { // Take Mutex
  ...; // โค้ดโปรแกรมส่วนที่ต้องการล็อกการทำงาน
  xSemaphoreGive(xPrintNumberMutex); // Give Mutex
}

ข้อมูลเพิ่มเติม


ดู 675 ครั้ง0 ความคิดเห็น

Comments


bottom of page