Arduino ESP32 Freertos互斥锁的优化使用方案

说明

未经本人同意,禁止任何形式的转载!

前言

在复杂函数和语句或异常处理代码中,主动对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:
}
### ESP32 中线程同步使用 Lock 实现互斥锁ESP32 的开发环境中,通常采用 FreeRTOS 提供的任务间同步机制来管理多个任务之间的访问冲突。对于互斥锁的应用场景而言,FreeRTOS 支持创建并配置多种类型的 mutex 来保护共享资源免受并发修改的影响。 为了实现在 ESP32 上利用 lock 进行线程同步的操作,下面给出一段基于 C 语言编写的代码片段作为演示: ```c #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" // 定义全局变用于存储互斥句柄 SemaphoreHandle_t xMutex; void TaskFunction(void *pvParameters) { // 尝试获取互斥锁 if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) { // 成功获取到互斥锁后的临界区代码... // 执行完成后再释放互斥锁 xSemaphoreGive(xMutex); } } void app_main() { // 创建一个二进制互斥型信号 xMutex = xSemaphoreCreateMutex(); // 如果创建失败则处理错误 if (xMutex != NULL) { // 启动两个新任务去争夺同一把锁 xTaskCreate(TaskFunction, "Task1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(TaskFunction, "Task2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 主循环保持运行状态 while(1){ vTaskDelay(pdMS_TO_TICKS(100)); } } } ``` 上述程序展示了如何初始化一个互斥锁 `xMutex` 并将其应用于两个独立的任务当中;每当某个特定任务想要执行某些关键操作之前都会先尝试占有这把锁,一旦成功便意味着其他任何试图占用它的进程都得暂时挂起直到前者主动放弃控制权为止[^4]。 值得注意的是,在实际项目里应当依据具体需求调整参数设置以及优化逻辑结构以确保最佳性能表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值