[ESP32] 6. Đồng bộ hóa tác vụ
Đồng bộ hóa tác vụ là quá trình điều phối các tác vụ để đảm bảo chúng hoạt động một cách nhịp nhàng và an toàn khi truy cập vào tài nguyên chung. Trong FreeRTOS, có nhiều cơ chế đồng bộ hóa khác nhau. Hiểu và sử dụng đúng các cơ chế đồng bộ hóa này sẽ giúp bạn xây dựng các ứng dụng đa tác vụ an toàn và hiệu quả trên ESP32.
6.1. Semaphores
Giải thích chi tiết:
Semaphore là một cơ chế đồng bộ hóa cho phép kiểm soát truy cập vào tài nguyên chia sẻ hoặc đồng bộ hóa các sự kiện giữa các tác vụ.
FreeRTOS cung cấp hai loại semaphore chính:
- Binary Semaphore: Chỉ có hai trạng thái (available hoặc not available).
- Counting Semaphore: Có thể có nhiều hơn một "token" available.
Ví dụ:
SemaphoreHandle_t xSemaphore = NULL;
void senderTask(void *pvParameters) {
while(1) {
xSemaphoreGive(xSemaphore);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void receiverTask(void *pvParameters) {
while(1) {
if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("Semaphore received");
}
}
}
void setup() {
Serial.begin(115200);
xSemaphore = xSemaphoreCreateBinary();
xTaskCreate(senderTask, "Sender", 1000, NULL, 1, NULL);
xTaskCreate(receiverTask, "Receiver", 1000, NULL, 1, NULL);
}
6.2. Mutexes
Giải thích chi tiết:
Mutex (Mutual Exclusion) là một dạng đặc biệt của binary semaphore, được sử dụng để bảo vệ tài nguyên chia sẻ khỏi truy cập đồng thời. Khác với semaphore thông thường, mutex có cơ chế "ownership", nghĩa là chỉ tác vụ đã lấy mutex mới có thể giải phóng nó.
Ví dụ:
SemaphoreHandle_t xMutex = NULL;
void sharedResourceTask(void *pvParameters) {
while(1) {
if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// Truy cập tài nguyên chia sẻ
Serial.println("Task accessing shared resource");
vTaskDelay(1000 / portTICK_PERIOD_MS);
xSemaphoreGive(xMutex);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
xMutex = xSemaphoreCreateMutex();
xTaskCreate(sharedResourceTask, "Task1", 1000, NULL, 1, NULL);
xTaskCreate(sharedResourceTask, "Task2", 1000, NULL, 1, NULL);
}
6.3. Event Groups
Giải thích chi tiết:
Event Groups cho phép nhiều tác vụ đồng bộ hóa trên nhiều sự kiện. Mỗi event group chứa một số bit, mỗi bit đại diện cho một sự kiện.
Ví dụ:
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
EventGroupHandle_t xEventGroup;
void eventSetterTask(void *pvParameters) {
while(1) {
xEventGroupSetBits(xEventGroup, BIT_0);
vTaskDelay(1000 / portTICK_PERIOD_MS);
xEventGroupSetBits(xEventGroup, BIT_1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void eventWaiterTask(void *pvParameters) {
EventBits_t xBits;
while(1) {
xBits = xEventGroupWaitBits(xEventGroup, BIT_0 | BIT_1, pdTRUE, pdTRUE, portMAX_DELAY);
if((xBits & (BIT_0 | BIT_1)) == (BIT_0 | BIT_1)) {
Serial.println("Both events occurred");
}
}
}
void setup() {
Serial.begin(115200);
xEventGroup = xEventGroupCreate();
xTaskCreate(eventSetterTask, "Setter", 1000, NULL, 1, NULL);
xTaskCreate(eventWaiterTask, "Waiter", 1000, NULL, 1, NULL);
}
6.4. Task Notifications
Giải thích chi tiết:
Task Notifications cung cấp một cách nhẹ và nhanh để gửi tín hiệu hoặc dữ liệu giữa các tác vụ. Mỗi tác vụ có một 32-bit notification value có thể được sử dụng như một event flag hoặc để truyền dữ liệu.
Ví dụ:
void senderTask(void *pvParameters) {
while(1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
xTaskNotify((TaskHandle_t)pvParameters, 0, eIncrement);
}
}
void receiverTask(void *pvParameters) {
uint32_t ulNotificationValue;
while(1) {
if(xTaskNotifyWait(0, ULONG_MAX, &ulNotificationValue, portMAX_DELAY) == pdTRUE) {
Serial.printf("Notification received: %lu\n", ulNotificationValue);
}
}
}
void setup() {
Serial.begin(115200);
TaskHandle_t xReceiverHandle;
xTaskCreate(receiverTask, "Receiver", 1000, NULL, 1, &xReceiverHandle);
xTaskCreate(senderTask, "Sender", 1000, (void *)xReceiverHandle, 1, NULL);
}
Lưu ý quan trọng:
- Chọn cơ chế đồng bộ hóa phù hợp với yêu cầu của ứng dụng.
- Semaphores và mutexes có thể dẫn đến priority inversion nếu không được sử dụng cẩn thận.
- Task Notifications thường nhanh hơn và sử dụng ít RAM hơn so với các cơ chế khác, nhưng có hạn chế về số lượng sự kiện có thể xử lý.
- Khi sử dụng bất kỳ cơ chế đồng bộ hóa nào, cần cẩn thận để tránh deadlocks.