news 2026/6/10 13:02:32

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

作者头像

张小明

前端开发工程师

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/6/10 12:24:01

【金融风险建模终极指南】:手把手教你用R语言实现Copula参数估计

第一章&#xff1a;金融风险建模中的Copula方法概述在金融风险管理中&#xff0c;资产收益之间的相关性结构对投资组合的风险评估至关重要。传统的线性相关系数&#xff08;如Pearson相关系数&#xff09;仅能捕捉变量间的线性关系&#xff0c;难以描述尾部依赖或非对称依赖结构…

作者头像 李华
网站建设 2026/6/10 12:24:50

手把手教你编译PHP 8.6扩展(含10个实用调试技巧)

第一章&#xff1a;PHP 8.6 扩展开发概述PHP 8.6 作为 PHP 语言演进中的重要版本&#xff0c;延续了对性能优化、类型系统增强和开发者体验提升的追求。在该版本中&#xff0c;扩展开发依然是深入底层、实现高性能功能模块的核心手段。无论是为实现特定算法加速、集成 C/C 库&a…

作者头像 李华
网站建设 2026/6/10 12:25:48

【权威发布】临床数据建模标准流程:基于R的Cox回归最佳实践

第一章&#xff1a;临床数据的 R 语言 Cox 回归优化在临床研究中&#xff0c;生存分析是评估患者预后的重要手段&#xff0c;而Cox比例风险模型因其能够处理删失数据并分析多因素影响&#xff0c;成为最常用的统计方法之一。利用R语言进行Cox回归建模&#xff0c;不仅可以高效实…

作者头像 李华
网站建设 2026/6/10 12:24:11

GitHub 热榜项目 - 日榜(2025-12-15)

GitHub 热榜项目 - 日榜(2025-12-15) 生成于&#xff1a;2025-12-15 统计摘要 共发现热门项目&#xff1a; 15 个 榜单类型&#xff1a;日榜 本期热点趋势总结 本期GitHub热榜彰显AI智能体开发与实用工具化的强劲势头开源项目sim和daytona分别聚焦AI工作流构建与代码执行基…

作者头像 李华
网站建设 2026/6/10 11:35:35

属性绑定如何提升PHP开发效率?资深架构师亲授实战心法

第一章&#xff1a;属性绑定的核心价值与PHP开发新范式属性绑定作为现代PHP开发中的关键技术&#xff0c;正在重塑开发者构建应用程序的方式。它通过将类属性与外部数据源&#xff08;如配置文件、数据库记录或HTTP请求参数&#xff09;建立动态关联&#xff0c;显著提升了代码…

作者头像 李华
网站建设 2026/6/10 0:38:20

为什么90%的医疗信息系统PHP审计都忽略了这个风险点?

第一章&#xff1a;医疗信息系统中PHP审计的现状与挑战在当前数字化转型加速的背景下&#xff0c;医疗信息系统&#xff08;HIS&#xff09;广泛采用PHP作为后端开发语言&#xff0c;因其部署灵活、生态成熟而受到中小型医疗机构青睐。然而&#xff0c;PHP代码的开放性与动态特…

作者头像 李华