说明
未经本人同意,禁止任何形式的转载!
前言
在复杂函数和语句或异常处理代码中,主动对Mutex的上锁和解锁操作容易出错,并且难以调试。举个栗子,在进入任务后成功锁定了Mutex,但是任务的具体逻辑比较复杂,可能存在很多if-else if-else或者switch,甚至某些条件分支存在return的情况,这个时候你可能要在多处对Mutex进行释放,稍不注意可能在某处忘记释放了就会导致任务无法继续对Mutex上锁。
解决方案
秉承“谁持有,谁释放”的基本原则,并且必须是先获取后释放,以下定义一个SmartLocker类,这是一个简化锁定和解锁互斥对象的方便类,实现互斥量的自动上锁和自动解锁,即构造函数实现上锁,析构函数实现解锁,如此SmartLocker的实例在离开作用域时就自动对Mutex解锁了。
注意:使用方法就是在需要上锁的地方创建一个SmartLocker局部对象,传入SemaphoreHandle_t变量和上锁的等待超时。
class SmartLocker
{
//#define debugEnable
public:
SmartLocker(SemaphoreHandle_t* mutex, TickType_t xTicksToWait):m_Mutex(mutex)
{
if(m_Mutex != NULL)
{
isLocked = xSemaphoreTake(*m_Mutex, xTicksToWait) == pdTRUE;
#ifdef debugEnable
if(isLocked)
{
Serial.println("xSemaphoreTake is ok.");
}
else
{
Serial.println("xSemaphoreTake is error.");
}
#endif
}
};
~SmartLocker()
{
if(isLocked)
{
xSemaphoreGive(*m_Mutex);
}
}
bool IsLocked() const
{
return isLocked;
}
private:
SemaphoreHandle_t* m_Mutex = NULL;
bool isLocked = false;
};
示例
以下为SmartLocker的使用示例,基于Arduino ESP32中的Mutex示例进行修改。在任务中使用SmartLocker代替xSemaphoreTake和xSemaphoreGive的操作,简化软件复杂度。对“#define UseMutex”进行注释/取消注释,观察串口输出即可了解使用和不使用互斥锁的区别。
SemaphoreHandle_t shared_var_mutex = NULL;
int shared_variable = 0;
#define UseMutex
void Task(void *pvParameters)
{
int task_num = *((int*)pvParameters);
Serial.printf("%s\n", task_num ? " Starting |" : " | Starting");
while(1)
{
#ifdef UseMutex
SmartLocker smartLocker(&shared_var_mutex, portMAX_DELAY);
if(smartLocker.IsLocked())
{
#endif
// Mutex successfully taken
int new_value = random(1000);
char str0[32]; sprintf(str0, " %d <- %d |", shared_variable, new_value);
char str1[32]; sprintf(str1, " | %d <- %d", shared_variable, new_value);
Serial.printf("%s\n", task_num ? str0 : str1);
shared_variable = new_value;
delay(random(100)); // wait random time of max 100 ms - simulating some computation
sprintf(str0, " R: %d |", shared_variable);
sprintf(str1, " | R: %d", shared_variable);
Serial.printf("%s\n", task_num ? str0 : str1);
if(shared_variable != new_value)
{
Serial.printf("%s\n", task_num ? " Mismatch! |" : " | Mismatch!");
}
#ifdef UseMutex
}
#endif
delay(10); // Allow other task to be scheduled
} // Infinite loop
}
void setup()
{
Serial.begin(115200);
while(!Serial) delay(100);
Serial.printf(" Task 0 | Task 1\n");
shared_var_mutex = xSemaphoreCreateMutex(); // Create the mutex
// Set up two tasks to run the same function independently.
static int task_number0 = 0;
xTaskCreate(
Task
, "Task 0" // A name just for humans
, 2048 // The stack size
, (void*)&task_number0 // Pass reference to a variable describing the task number
, 1 // priority
, NULL // Task handle is not used here - simply pass NULL
);
static int task_number1 = 1;
xTaskCreate(
Task
, "Task 1"
, 2048 // Stack size
, (void*)&task_number1 // Pass reference to a variable describing the task number
, 1 // Low priority
, NULL // Task handle is not used here - simply pass NULL
);
}
void loop() {
// put your main code here, to run repeatedly:
}
后感
编写SmartLocker类的灵感是自己学习Freertos时突发奇想,参考Qt中的QMutexLocker实现的。Freertos中互斥锁、信号量、队列等内容跟windows、linux平台上线程同步的概念是类似的,如果是学过线程同步的概念再去学习Freertos就容易得多,反之则不然。目前大多数的Freertos教程是在单片机上做的,而且讲的很迷,对于初学者自学来说是很抽象的。如果你学过面向对象编程并且了解线程同步相关的概念,再学习嵌入式的rtos就比较容易理解了。这里纯属我自己的理解,如果你不同意我的观点,那就是你对!
完整代码
class SmartLocker
{
//#define debugEnable
public:
SmartLocker(SemaphoreHandle_t* mutex, TickType_t xTicksToWait):m_Mutex(mutex)
{
if(m_Mutex != NULL)
{
isLocked = xSemaphoreTake(*m_Mutex, xTicksToWait) == pdTRUE;
#ifdef debugEnable
if(isLocked)
{
Serial.println("xSemaphoreTake is ok.");
}
else
{
Serial.println("xSemaphoreTake is error.");
}
#endif
}
};
~SmartLocker()
{
if(isLocked)
{
xSemaphoreGive(*m_Mutex);
}
}
bool IsLocked() const
{
return isLocked;
}
private:
SemaphoreHandle_t* m_Mutex = NULL;
bool isLocked = false;
};
SemaphoreHandle_t shared_var_mutex = NULL;
int shared_variable = 0;
#define UseMutex
void Task(void *pvParameters)
{
int task_num = *((int*)pvParameters);
Serial.printf("%s\n", task_num ? " Starting |" : " | Starting");
while(1)
{
#ifdef UseMutex
SmartLocker smartLocker(&shared_var_mutex, portMAX_DELAY);
if(smartLocker.IsLocked())
{
#endif
// Mutex successfully taken
int new_value = random(1000);
char str0[32]; sprintf(str0, " %d <- %d |", shared_variable, new_value);
char str1[32]; sprintf(str1, " | %d <- %d", shared_variable, new_value);
Serial.printf("%s\n", task_num ? str0 : str1);
shared_variable = new_value;
delay(random(100)); // wait random time of max 100 ms - simulating some computation
sprintf(str0, " R: %d |", shared_variable);
sprintf(str1, " | R: %d", shared_variable);
Serial.printf("%s\n", task_num ? str0 : str1);
if(shared_variable != new_value)
{
Serial.printf("%s\n", task_num ? " Mismatch! |" : " | Mismatch!");
}
#ifdef UseMutex
}
#endif
delay(10); // Allow other task to be scheduled
} // Infinite loop
}
void setup()
{
Serial.begin(115200);
while(!Serial) delay(100);
Serial.printf(" Task 0 | Task 1\n");
shared_var_mutex = xSemaphoreCreateMutex(); // Create the mutex
// Set up two tasks to run the same function independently.
static int task_number0 = 0;
xTaskCreate(
Task
, "Task 0" // A name just for humans
, 2048 // The stack size
, (void*)&task_number0 // Pass reference to a variable describing the task number
, 1 // priority
, NULL // Task handle is not used here - simply pass NULL
);
static int task_number1 = 1;
xTaskCreate(
Task
, "Task 1"
, 2048 // Stack size
, (void*)&task_number1 // Pass reference to a variable describing the task number
, 1 // Low priority
, NULL // Task handle is not used here - simply pass NULL
);
}
void loop() {
// put your main code here, to run repeatedly:
}