C++ 信号量

C++ 信号量(Semaphore)详解与编程实践 信号量是多线程同步与互斥的核心工具之一,它能有效解决多线程间的资源竞争、任务协同问题。本文将从信号量的核心概念出发,逐步讲解其在 C++ 中的实现与使用,帮助你快速掌握这一关键技术。 一、信号量的核心概念 1. 什么是信号量? 信号量(Semaphore)本质上是一个计数器+等待 / 唤醒机制,用于控制多个线程对共享资源的访问权限,或实现线程间的有序协同。 它由荷兰计算机科学家 Dijkstra 于 1965 年提出,核心有两个操作(原子操作,不可被中断): P 操作(申请资源):也叫wait操作,将信号量计数器减 1。如果减 1 后计数器≥0,说明申请资源成功,线程继续执行;如果计数器<0,说明资源耗尽,线程被阻塞(进入等待队列),直到有其他线程释放资源。 V 操作(释放资源):也叫post操作,将信号量计数器加 1。如果加 1 后计数器≤0,说明有线程正在等待该资源,会唤醒一个等待队列中的线程,让其继续执行;如果计数器>0,说明没有线程等待,仅完成计数器更新。 2. 信号量的两种核心类型 (1) 互斥信号量(二进制信号量) 计数器的值只能是0或1,等价于一个 “互斥锁”,用于解决临界区资源的互斥访问(同一时间只有一个线程能访问共享资源)。 初始值为1,表示资源可用。 线程访问资源前执行 P 操作,计数器变为0(资源被占用)。 线程释放资源后执行 V 操作,计数器变回1(资源释放)。 如果有其他线程此时申请资源,会因计数器变为-1而被阻塞,直到持有资源的线程释放。 (2) 计数信号量(通用信号量) 计数器的值可以是任意非负整数,用于控制有限个共享资源的并发访问(同一时间允许 N 个线程访问共享资源)。 初始值为N(N 为共享资源的数量,例如线程池的最大并发数、缓冲区的容量)。 每个线程申请资源时执行 P 操作,计数器减 1;释放资源时执行 V 操作,计数器加 1。 当计数器变为0时,后续申请资源的线程会被阻塞,直到有线程释放资源。 二、C++ 中的信号量实现 C++ 标准库在C++20中才正式引入了信号量相关接口(位于<semaphore>头文件),在此之前,开发者通常使用第三方库(如 Boost)或操作系统提供的接口(如 Linux 的<semaphore.h>、Windows 的CreateSemaphore)。 本文将讲解两种常用实现: C++20 标准信号量(推荐,跨平台) Linux 系统信号量(兼容旧版本 C++,仅适用于 Linux/Unix) 前置说明 本文所有示例均基于多线程环境,需包含<thread>头文件,编译时需链接线程库(GCC 编译器添加-pthread参数)。 示例代码注重可读性,简化了部分异常处理,生产环境中需补充完善。 三、C++20 标准信号量实战 C++20 提供了两种标准信号量: std::counting_semaphore<>:计数信号量,模板参数为计数器的最大值(需为非负整数)。 std::binary_semaphore:二进制信号量,是std::counting_semaphore<1>的别名,等价于互斥信号量。 核心成员函数 函数 功能 对应操作 void acquire() 申请资源(P 操作),若资源不足则阻塞线程 P 操作 bool try_acquire() 尝试申请资源,成功返回true,失败返回false(不阻塞) 非阻塞 P 操作 void release(ptrdiff_t update = 1) 释放资源(V 操作),update为计数器增加的值(默认 1) V 操作 示例 1:二进制信号量实现互斥访问 需求:两个线程同时对同一个全局变量进行累加操作,使用std::binary_semaphore保证操作的原子性,避免数据竞争。 cpp 运行 #include <iostream> #include <thread> #include <semaphore> // C++20 标准信号量头文件 // 全局共享资源 int g_shared_count = 0; // 二进制信号量,初始值为1(资源可用) std::binary_semaphore g_binary_sem(1); // 累加任务函数 void increment_count(int times) { for (int i = 0; i < times; ++i) { // P操作:申请资源,进入临界区 g_binary_sem.acquire(); // 临界区:修改共享变量(原子操作,避免数据混乱) g_shared_count++; // 打印当前线程ID和累加后的值(仅用于演示) std::cout << "Thread " << std::this_thread::get_id() << " : g_shared_count = " << g_shared_count << std::endl; // V操作:释放资源,退出临界区 g_binary_sem.release(); } } int main() { // 创建两个线程,每个线程累加10次 std::thread t1(increment_count, 10); std::thread t2(increment_count, 10); // 等待两个线程执行完毕 t1.join(); t2.join(); // 打印最终结果 std::cout << "Final g_shared_count = " << g_shared_count << std::endl; return 0; } 编译与运行(GCC) bash 运行 # 需开启C++20标准,链接线程库 g++ -std=c++20 semaphore_binary.cpp -o semaphore_binary -pthread ./semaphore_binary 运行结果说明 两个线程不会同时修改g_shared_count,最终结果稳定为20,不会出现数据丢失(若不使用信号量,最终结果可能小于20)。 示例 2:计数信号量实现资源限流 需求:创建 5 个线程,模拟访问一个最多允许 2 个线程同时访问的共享资源,使用std::counting_semaphore实现限流。 cpp 运行 #include <iostream> #include <thread> #include <semaphore> #include <chrono> // 计数信号量,初始值为2(最多允许2个线程同时访问资源) std::counting_semaphore<10> g_counting_sem(2); // 最大值设为10,满足需求即可 // 资源访问任务函数 void access_resource(int thread_id) { // P操作:申请访问资源 g_counting_sem.acquire(); std::cout << "Thread " << thread_id << " : 成功获取资源,开始访问..." << std::endl; // 模拟资源访问耗时(休眠2秒) std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "Thread " << thread_id << " : 资源访问完毕,释放资源..." << std::endl; // V操作:释放资源 g_counting_sem.release(); } int main() { const int thread_num = 5; std::thread threads[thread_num]; // 创建5个线程 for (int i = 0; i < thread_num; ++i) { threads[i] = std::thread(access_resource, i + 1); } // 等待所有线程执行完毕 for (int i = 0; i < thread_num; ++i) { threads[i].join(); } std::cout << "所有线程均完成资源访问" << std::endl; return 0; } 运行结果说明 运行后会发现,同一时间只有 2 个线程在访问资源,其余线程处于阻塞状态,直到有线程释放资源后,才会有新的线程获取资源并执行。 四、Linux 系统信号量(兼容旧版本 C++) 对于不支持 C++20 的环境,Linux/Unix 系统提供了<semaphore.h>头文件,实现了 POSIX 标准信号量,常用的有命名信号量(用于进程间通信)和无名信号量(用于线程间通信),本文重点讲解线程间通信的无名信号量。 核心函数 初始化信号量 c 运行 int sem_init(sem_t *sem, int pshared, unsigned int value); 参数 1:sem:指向要初始化的信号量对象。 参数 2:pshared:是否用于进程间共享,0表示仅用于线程间共享,非0表示用于进程间共享。 参数 3:value:信号量初始值(计数器初始值)。 返回值:成功返回0,失败返回-1。 P 操作(申请资源) c 运行 int sem_wait(sem_t *sem); 阻塞式申请资源,计数器减 1,资源不足则阻塞线程。 成功返回0,失败返回-1。 V 操作(释放资源) c 运行 int sem_post(sem_t *sem); 释放资源,计数器加 1,唤醒等待队列中的线程。 成功返回0,失败返回-1。 销毁信号量 c 运行 int sem_destroy(sem_t *sem); 释放信号量占用的资源,仅能销毁sem_init初始化的无名信号量。 成功返回0,失败返回-1。 示例:Linux 无名信号量实现线程协同 需求:实现 “生产者 - 消费者” 简单模型,生产者线程生产数据(存入全局变量),消费者线程消费数据,使用两个二进制信号量实现线程间协同。 cpp 运行 #include <iostream> #include <thread> #include <chrono> #include <semaphore.h> // Linux 信号量头文件 // 全局共享数据 int g_data = 0; // 两个二进制信号量 sem_t g_producer_sem; // 生产者信号量,初始值1(允许生产) sem_t g_consumer_sem; // 消费者信号量,初始值0(无数据可消费) // 生产者线程函数 void producer() { for (int i = 0; i < 5; ++i) { // P操作:申请生产权限 sem_wait(&g_producer_sem); // 生产数据 g_data = i + 1; std::cout << "生产者:生产数据 " << g_data << std::endl; // V操作:通知消费者可以消费 sem_post(&g_consumer_sem); // 模拟生产间隔 std::this_thread::sleep_for(std::chrono::seconds(1)); } } // 消费者线程函数 void consumer() { for (int i = 0; i < 5; ++i) { // P操作:申请消费权限(无数据则阻塞) sem_wait(&g_consumer_sem); // 消费数据 std::cout << "消费者:消费数据 " << g_data << std::endl; // V操作:通知生产者可以继续生产 sem_post(&g_producer_sem); // 模拟消费间隔 std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { // 初始化信号量 sem_init(&g_producer_sem, 0, 1); sem_init(&g_consumer_sem, 0, 0); // 创建生产者和消费者线程 std::thread t_producer(producer); std::thread t_consumer(consumer); // 等待线程执行完毕 t_producer.join(); t_consumer.join(); // 销毁信号量 sem_destroy(&g_producer_sem); sem_destroy(&g_consumer_sem); return 0; } 编译与运行(GCC) bash 运行 # 无需C++20标准,链接线程库即可 g++ semaphore_linux.cpp -o semaphore_linux -pthread ./semaphore_linux 运行结果说明 生产者和消费者线程有序执行,生产者生产一个数据后,必须等待消费者消费完毕才能继续生产,消费者也只能在生产者生产后才能消费,实现了完美的线程协同。 五、信号量的常见应用场景 临界区资源互斥访问:使用二进制信号量替代互斥锁(std::mutex),实现共享资源(如全局变量、文件句柄)的原子操作。 资源限流:使用计数信号量控制并发访问数(如线程池最大并发数、接口限流、连接池最大连接数)。 线程间协同:实现 “生产者 - 消费者”“读者 - 写者” 等模型,解决线程间的等待 / 通知问题。 进程间通信:使用 Linux 命名信号量或 Windows 系统信号量,实现不同进程间的同步与互斥。 六、注意事项 避免死锁:申请信号量后,必须保证在所有分支(包括异常分支)中释放信号量,否则会导致其他线程永久阻塞。 C++20 兼容性:std::semaphore仅在 C++20 及以上标准支持,使用前需确认编译器版本(GCC 10+、Clang 11+、MSVC 2019+ 支持 C++20)。 信号量与互斥锁的区别: 互斥锁(std::mutex)只能由持有锁的线程释放,信号量可以由任意线程释放。 信号量支持计数限流,互斥锁仅支持单线程互斥访问。 互斥锁的性能通常优于二进制信号量,简单互斥场景优先使用互斥锁。 Linux 信号量初始化:无名信号量用于线程间通信时,pshared参数必须设为0。 总结 信号量是由 “计数器 + 等待 / 唤醒机制” 组成的同步工具,核心操作是 P(acquire/wait)和 V(release/post),且这两个操作均为原子操作。 信号量分为二进制信号量(互斥,0/1)和计数信号量(限流,非负整数),C++20 提供了标准实现,Linux 提供了<semaphore.h>兼容旧版本。 信号量可用于解决资源互斥、限流、线程协同等问题,使用时需避免死锁,简单互斥场景优先选择互斥锁以获得更好性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1215980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

老年人能力评估系统开发Day2

前几天构出了系统的大纲,这几天在制作系统主要框架 每天添加一些可能会使用的内容,修改系统完善,让它先能完整跑出来 后面再继续完善完善细节

2026年乌鲁木齐GEO优化公司推荐TOP3:产业深度适配+全链路效果的选型指南

2026年乌鲁木齐GEO优化公司推荐TOP3:产业深度适配+全链路效果的选型指南 聊起乌鲁木齐的企业营销,最近常听老板们念叨:“现在客户查纺织面料、找景区攻略、问新能源配件,先戳AI助手搜一搜已成习惯——咱的业务咋才…

2025年教我学英语 - 玩

2025年教我学英语 - 玩1、玩耍、嬉戏 - play [pleɪ] 游戏、运动 - game [ɡeɪm] 玩具 - toy [tɔɪ] 积木 - block [blɒk] 玩偶、娃娃 - doll [dɒl]2、拼图 - puzzle [ˈpʌzl] 风筝 - kite [kaɪt] 陀螺 - top [t…

陀螺仪原理、关键算法、典型应用与软件实现详解

目录 一、陀螺仪核心原理 1. 基本物理定律&#xff1a;角动量守恒 2. 主流陀螺仪类型及工作机制 3. MEMS 陀螺仪的核心工作流程&#xff08;嵌入式场景重点&#xff09; 4. 关键性能参数&#xff08;嵌入式选型依据&#xff09; 二、陀螺仪关键算法 1. 基础数据处理算法…

一天一个开源项目(第2篇):Remotion - 用 React 程序化创建视频

引言 “如果视频制作也能像写代码一样&#xff0c;用组件、函数和逻辑来构建&#xff0c;那该多好&#xff1f;” 这是"一天一个开源项目"系列的第2篇文章。今天带你了解的项目是 Remotion&#xff08;GitHub&#xff09;。 想象一下&#xff0c;你不再需要打开 Aft…

[ai编程]vibe coding心得

视频力作bush【用AI写代码不是这样的呀!!!-bilibili】 https://b23.tv/lE7rNmS 你为什么要这样&#xff1f;…… “写代码不是这样的呀” 你一上来就把问题丢给 AI&#xff0c;然后拿一段“看起来很完整”的解释来糊弄你的SDK工程&#xff0c;不期望生成模块库的现场&#xff…

对比与反差

学 OI 时,回到教室,感觉是无比温暖舒适,还有三五好友为伴,打闹玩乐,太热闹了,回到机房后,又是冰冷的机器与无情的 WA,还有压抑的环境。 回归高考后,回到机房,感觉是无比温馨安宁,时间在这里停滞,可以静下来…

从 Prompt 到产品:Gemini 在 WebApp 项目中的提示词与实践

img { display: block; margin-left: auto; margin-right: auto } table { margin-left: auto; margin-right: auto } 在人工智能快速发展的时代,理解深度学习模型的内部机制已经成为研究与实践的重要课题。BP 神经网…

Atcoder Educational DP Contest

A 每个点可以转移到后面的两个点。AC Code #include<bits/stdc++.h> using namespace std; #define int long long #define INF (int)(1e9) #define fir first #define sec secondconst int N=1e5+9,M=(1<<…

FastAPI多进程部署:定时任务重复执行?手把手教你用锁搞定

本文深入探讨了FastAPI应用在多进程部署时遇到的定时任务重复执行、共享资源竞争及依赖重复初始化三大典型问题。通过引入跨进程文件锁机制,提供了清晰的解决方案和可直接复用的代码示例,帮助开发者在提升应用并发性…

引用详解:C++ 引用与指针的区别及使用场景

引用详解&#xff1a;C 引用与指针的区别及使用场景 在 C 编程中&#xff0c;引用&#xff08;Reference&#xff09;是与指针并列的核心语法特性&#xff0c;二者都能实现对变量的间接访问&#xff0c;提升代码的灵活性与效率。但引用并非指针的“简化版”&#xff0c;其本质…

别再当“年费冤大头”!我这样用AI,一年省下上千块

不知道你有没有这种感觉&#xff1a;买了个AI年费会员&#xff0c;结果一年到头用不了几次&#xff0c;一千多块钱就这么“睡”在账户里&#xff0c;心疼又无奈。我去年就用过一个国外模型&#xff0c;年费一千四。后来技术问题自己解决了&#xff0c;用得越来越少&#xff0c;…

10387_基于SpringBoot的学生成绩管理系统

1、项目包含 项目源码、项目文档、数据库脚本、软件工具等资料; 带你从零开始部署运行本套系统。 2、技术说明 后端:SpringBoot 前端:VUE 数据库:MySql 开发工具:JDK1.8及以上 + Eclipse + MySQL + Maven 本项目涉…

2026年厦门GEO优化公司推荐TOP3:从技术实力到效果落地的深度测评

2026年厦门GEO优化公司推荐TOP3:从技术实力到效果落地的深度测评 AI搜索时代,GEO(生成引擎优化) 已经成为企业在AI生态中抢占流量的核心武器——它直接决定了你的品牌能否出现在AI助手的推荐列表里,能否被潜在客…

2026年苏州GEO优化公司推荐TOP3:从技术实力到效果落地的深度测评

2026年苏州GEO优化公司推荐TOP3:从技术实力到效果落地的深度测评 AI搜索早已不是“尝鲜”,而是企业抢占流量的“必选项”——但能把GEO优化做深做透的公司,在苏州其实没那么多。对于做智能制造、文创、跨境电商的苏…

【博弈论 Nim问题】洛谷 P2197 【模板】Nim 游戏

View Post【博弈论 Nim问题】洛谷 P2197 【模板】Nim 游戏题目 https://www.luogu.com.cn/problem/P2197 题解 经典 Nim 游戏是数学领域的公平组合数学博弈论问题,公平组合游戏具备以下特征:完全性(完全信息) 确定…

深度测评8个论文写作工具,MBA必备一键生成论文工具推荐!

深度测评8个论文写作工具&#xff0c;MBA必备一键生成论文工具推荐&#xff01; AI 工具助力论文写作&#xff0c;MBA 人的高效利器 在当前学术环境日益严格的背景下&#xff0c;MBA 学生和研究者们面临着越来越高的论文写作要求。从选题、大纲构建到初稿撰写&#xff0c;再到查…

人类学史上的里程碑 ——《忧郁的热带》书评与推荐:一部跨越七十年的思想经典

人类学史上的里程碑&#xff1a;《忧郁的热带》书评与推荐一部跨越七十年的思想经典在人类学思想的浩瀚星空中&#xff0c;克洛德列维-施特劳斯的《忧郁的热带》犹如一颗永不熄灭的恒星&#xff0c;自1955年问世以来&#xff0c;便以其深邃的哲学思考、精致的文学笔触和开创性的…

Python中的Mixin继承:灵活组合功能的强大模式

Python中的Mixin继承&#xff1a;灵活组合功能的强大模式 1. 什么是Mixin继承&#xff1f;2. Mixin与传统继承的区别3. Python中实现Mixin的最佳实践3.1 命名约定3.2 避免状态初始化3.3 功能单一性 4. 实际应用案例4.1 Django中的Mixin应用4.2 DRF (Django REST Framework)中的…

《算法通关指南:数据结构和算法篇 --- 顺序表相关算法题》--- 1.移动零,2.颜色分类 - 指南

《算法通关指南:数据结构和算法篇 --- 顺序表相关算法题》--- 1.移动零,2.颜色分类 - 指南2026-01-25 22:24 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !import…