news 2026/4/29 17:27:12

为什么二值信号量先释放,再获得,互斥量先获得,再释放

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
为什么二值信号量先释放,再获得,互斥量先获得,再释放

我是嵌入式学习菌,一名热爱学习的嵌入式工程师

关注我,一起变得更加优秀!

CSDN、B 站视频号同名同步分享嵌入式学习点滴~ 无捷径唯有坚持,愿与你并肩稳步前行!
15篇原创内容
公众号

要理解二值信号量“先释放后获取”和互斥量“先获取后释放”的核心差异,本质是两者的设计目的和应用场景完全不同:

二值信号量:核心是同步(事件通知)——一个任务/中断通知另一个任务“某个事件已发生”,因此“事件产生方(释放信号量)”先触发,“事件消费方(获取信号量)”后响应;

互斥量:核心是互斥(资源保护)——保护多个任务共享的资源(如串口、全局变量),必须先“抢占锁(获取互斥量)”才能访问资源,用完后“释放锁(释放互斥量)”,否则会导致资源访问混乱。

以下结合ESP32+ESP-IDF环境给出具体示例,并解释顺序的必要性。

一、二值信号量:先释放、后获取(同步场景)

核心逻辑

二值信号量是“事件通知工具”:只有当「事件产生方」完成事件(如传感器采集完成、数据接收完成),才会释放信号量;「事件消费方」一直等待信号量,直到收到通知才执行后续逻辑。如果反过来“先获取后释放”,消费方会因信号量初始值为0而永久阻塞,程序完全无法运行。

ESP32IDF示例代码(同步场景:传感器采集→数据处理)

#include<stdio.h>#include"freertos/FreeRTOS.h"#include"freertos/task.h"#include"freertos/semphr.h"#include"esp_log.h"#defineTAG"BIN_SEM"staticSemaphoreHandle_txBinarySem=NULL;//任务1:事件产生方(传感器采集数据,完成后释放信号量)staticvoidtask_sensor_collect(void*arg){(void)arg;TickType_txLastWakeTime=xTaskGetTickCount();constTickType_txPeriod=pdMS_TO_TICKS(3000);//3秒采集一次for(;;){vTaskDelayUntil(&xLastWakeTime,xPeriod);//模拟:传感器采集数据完成(事件发生)ESP_LOGI(TAG,"任务1:传感器数据采集完成→释放信号量(发通知)");xSemaphoreGive(xBinarySem);//先释放:事件发生,发通知}vTaskDelete(NULL);}//任务2:事件消费方(等待信号量,收到通知后处理数据)staticvoidtask_data_process(void*arg){(void)arg;for(;;){//后获取:等待“采集完成”的通知(信号量)xSemaphoreTake(xBinarySem,portMAX_DELAY);ESP_LOGI(TAG,"任务2:收到信号量→开始处理传感器数据\n");}vTaskDelete(NULL);}voidapp_main(void){//创建二值信号量(初始值为0:无事件通知)xBinarySem=xSemaphoreCreateBinary();if(xBinarySem==NULL){ESP_LOGE(TAG,"信号量创建失败");return;}//创建任务(任务1优先级2>任务2优先级1,确保释放后立即调度)xTaskCreate(task_sensor_collect,"sensor",2048,NULL,2,NULL);xTaskCreate(task_data_process,"process",2048,NULL,1,NULL);}

运行效果(关键看顺序)

I(3015)BIN_SEM:任务1:传感器数据采集完成→释放信号量(发通知)

I(3015)BIN_SEM:任务2:收到信号量→开始处理传感器数据

I(6015)BIN_SEM:任务1:传感器数据采集完成→释放信号量(发通知)

I(6015)BIN_SEM:任务2:收到信号量→开始处理传感器数据

为什么必须“先释放后获取”?

二值信号量初始值为0,若任务2先调用xSemaphoreTake(),会直接阻塞;只有任务1先调用xSemaphoreGive()将信号量置1,任务2才能获取到信号量并执行——这正是“事件通知”的核心:先有事件,后有响应。

二、互斥量:先获取、后释放(互斥场景)

核心逻辑

互斥量是“资源锁”:多个任务访问共享资源(如串口、全局变量、硬件外设)时,必须先“上锁(获取互斥量)”,确保只有自己能访问资源;用完后“解锁(释放互斥量)”,其他任务才能抢占。如果反过来“先释放后获取”,会因“锁未被持有”导致释放失败(返回pdFALSE),且共享资源会被多个任务同时访问,造成数据混乱/打印错乱。

ESP32IDF示例代码(互斥场景:两个任务共享串口打印)

#include<stdio.h>#include"freertos/FreeRTOS.h"#include"freertos/task.h"#include"freertos/semphr.h"#include"esp_log.h"#defineTAG"MUTEX_DEMO"staticSemaphoreHandle_txMutex=NULL;//互斥量句柄staticintg_shared_count=0;//共享全局变量//任务1:访问共享资源(先获取互斥量,后释放)staticvoidtask1_access_resource(void*arg){(void)arg;for(;;){//第一步:先获取互斥量(上锁)→独占共享资源if(xSemaphoreTake(xMutex,portMAX_DELAY)==pdTRUE){//临界区:访问共享资源(全局变量+串口打印)g_shared_count++;ESP_LOGI(TAG,"任务1:持有互斥量→共享变量值:%d",g_shared_count);vTaskDelay(pdMS_TO_TICKS(500));//模拟资源占用时间//第二步:释放互斥量(解锁)→允许其他任务访问xSemaphoreGive(xMutex);ESP_LOGI(TAG,"任务1:释放互斥量\n");}vTaskDelay(pdMS_TO_TICKS(1000));//任务1执行间隔}vTaskDelete(NULL);}//任务2:访问同一个共享资源(同样先获取、后释放)staticvoidtask2_access_resource(void*arg){(void)arg;for(;;){//第一步:先获取互斥量(上锁)if(xSemaphoreTake(xMutex,portMAX_DELAY)==pdTRUE){//临界区:访问共享资源g_shared_count++;ESP_LOGI(TAG,"任务2:持有互斥量→共享变量值:%d",g_shared_count);vTaskDelay(pdMS_TO_TICKS(500));//模拟资源占用时间//第二步:释放互斥量(解锁)xSemaphoreGive(xMutex);ESP_LOGI(TAG,"任务2:释放互斥量\n");}vTaskDelay(pdMS_TO_TICKS(1000));//任务2执行间隔}vTaskDelete(NULL);}voidapp_main(void){//创建互斥量(ESP-IDF中xSemaphoreCreateMutex()初始值为1:未上锁)xMutex=xSemaphoreCreateMutex();if(xMutex==NULL){ESP_LOGE(TAG,"互斥量创建失败");return;}//创建两个优先级相同的任务(模拟资源竞争)xTaskCreate(task1_access_resource,"task1",2048,NULL,1,NULL);xTaskCreate(task2_access_resource,"task2",2048,NULL,1,NULL);}

运行效果(关键:无资源竞争,打印有序)

I(1015)MUTEX_DEMO:任务1:持有互斥量→共享变量值:1

I(1515)MUTEX_DEMO:任务1:释放互斥量

I(2015)MUTEX_DEMO:任务2:持有互斥量→共享变量值:2

I(2515)MUTEX_DEMO:任务2:释放互斥量

I(3015)MUTEX_DEMO:任务1:持有互斥量→共享变量值:3

I(3515)MUTEX_DEMO:任务1:释放互斥量

若反过来“先释放后获取”会怎样?

如果把任务1的逻辑改成:

//错误示例:先释放(未持有锁),后获取xSemaphoreGive(xMutex);//释放未持有的互斥量→返回pdFALSE,无效果xSemaphoreTake(xMutex,portMAX_DELAY);

运行结果:

xSemaphoreGive()返回pdFALSE(释放失败,因为任务1未持有互斥量);

两个任务会同时进入临界区,共享变量打印错乱(比如同时打印g_shared_count,出现值重复/跳变):

plaintext

I(1015)MUTEX_DEMO:任务1:持有互斥量→共享变量值:1

I(1015)MUTEX_DEMO:任务2:持有互斥量→共享变量值:1//数据错乱

为什么必须“先获取后释放”?

互斥量初始值为1(“未上锁”),只有先调用xSemaphoreTake()将其置0(“上锁”),才能独占共享资源;用完后调用xSemaphoreGive()置1(“解锁”),其他任务才能继续抢占——这是“资源保护”的核心:先占锁,再用资源,用完解锁。

三、核心区别总结

特性二值信号量互斥量

核心目的同步(事件通知)互斥(资源保护)

初始值0(无事件)1(未上锁)

操作顺序先释放(发通知)→后获取(收通知)先获取(上锁)→后释放(解锁)

失败后果消费方阻塞(无事件响应)资源竞争(数据/打印错乱)

典型场景传感器采集→数据处理、中断通知任务多任务访问串口/全局变量/外设

一句话记忆:

二值信号量是“发消息”:先有人发(释放),才能有人收(获取);

互斥量是“借钥匙”:先借到钥匙(获取),才能开门用资源,用完还钥匙(释放)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 9:25:45

2025如何选择适合企业规模和需求的财税外包服务

随着企业不断发展,财税管理已成为不可忽视的重要部分。尤其对于中小型企业来说,如何选择一款既能保证税务合规又能够提供个性化解决方案的财税服务显得尤为重要。在众多的财税服务提供商中,如何在定制化和标准化服务之间做出正确选择?本文将帮助您解答这一问题,并为您推荐…

作者头像 李华
网站建设 2026/4/27 0:28:32

【紧急必读】R Shiny多模态更新卡顿?这4个性能优化方案必须掌握

第一章&#xff1a;R Shiny多模态更新卡顿问题的现状与挑战在构建交互式数据可视化应用时&#xff0c;R Shiny 成为数据科学家和开发者的首选工具。然而&#xff0c;随着应用复杂度提升&#xff0c;尤其是涉及多模态输入&#xff08;如文件上传、滑块调节、下拉选择等&#xff…

作者头像 李华
网站建设 2026/4/28 4:32:48

《长安二十四计》盛大启幕,徐璐担纲女主,携手成毅开启长安风云

古装权谋大剧《长安二十四计》于12月12日正式开播&#xff0c;登陆央视八套黄金强档&#xff0c;并在优酷、咪咕等网络平台同步上线&#xff0c;拉开了这幅描绘盛唐智计风云的磅礴画卷。该剧由成毅、徐璐分别担任男女主&#xff0c;更云集了刘奕君、王劲松、倪大红、张涵予等十…

作者头像 李华
网站建设 2026/4/22 13:25:03

【Excel VBA 编程】第59讲:VBA正则的隐形助手——非捕获组(?:)

上一期讲到了捕获组&#xff0c;它尽职尽责地找到我们关心的文本模式&#xff0c;并将其分门别类地记录到 SubMatches 集合中。然而&#xff0c;当匹配逻辑变得复杂时&#xff0c;这位“助手”过于细致的记录有时反而会成为一种负担什么是非捕获组为了卸下负担&#xff0c;解决…

作者头像 李华
网站建设 2026/4/16 18:22:39

构建高效协作的软件测试团队:策略与实践

在当今快速迭代的软件开发环境中&#xff0c;测试团队不再是简单的“bug发现者”&#xff0c;而是保障产品质量、推动技术创新的关键力量。随着敏捷开发、DevOps和持续集成/持续部署&#xff08;CI/CD&#xff09;的普及&#xff0c;测试工作面临着更高的效率要求和更复杂的协作…

作者头像 李华
网站建设 2026/4/23 18:35:45

为什么你的Rust PHP扩展总是崩溃?深入调试核心函数的3大方法

第一章&#xff1a;为什么你的Rust PHP扩展总是崩溃&#xff1f;在尝试将 Rust 与 PHP 集成以提升性能时&#xff0c;许多开发者遭遇运行时崩溃、段错误或不可预测的行为。根本原因往往并非语言本身的问题&#xff0c;而是对 PHP 扩展生命周期和内存管理模型的误解。不正确的内…

作者头像 李华