购物网站 建设 费用企业设计公司
2026/4/6 5:25:07 网站建设 项目流程
购物网站 建设 费用,企业设计公司,修改wordpress代码加快打开速度,wordpress添加下载文件C 信号量#xff08;Semaphore#xff09;详解与编程实践 信号量是多线程同步与互斥的核心工具之一#xff0c;它能有效解决多线程间的资源竞争、任务协同问题。本文将从信号量的核心概念出发#xff0c;逐步讲解其在 C 中的实现与使用#xff0c;帮助你快速掌握这一关键技…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 个线程访问共享资源。 初始值为NN 为共享资源的数量例如线程池的最大并发数、缓冲区的容量。 每个线程申请资源时执行 P 操作计数器减 1释放资源时执行 V 操作计数器加 1。 当计数器变为0时后续申请资源的线程会被阻塞直到有线程释放资源。 二、C 中的信号量实现 C 标准库在C20中才正式引入了信号量相关接口位于semaphore头文件在此之前开发者通常使用第三方库如 Boost或操作系统提供的接口如 Linux 的semaphore.h、Windows 的CreateSemaphore。 本文将讲解两种常用实现 C20 标准信号量推荐跨平台 Linux 系统信号量兼容旧版本 C仅适用于 Linux/Unix 前置说明 本文所有示例均基于多线程环境需包含thread头文件编译时需链接线程库GCC 编译器添加-pthread参数。 示例代码注重可读性简化了部分异常处理生产环境中需补充完善。 三、C20 标准信号量实战 C20 提供了两种标准信号量 std::counting_semaphore计数信号量模板参数为计数器的最大值需为非负整数。 std::binary_semaphore二进制信号量是std::counting_semaphore1的别名等价于互斥信号量。 核心成员函数 函数 功能 对应操作 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 // C20 标准信号量头文件 // 全局共享资源 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 运行 # 需开启C20标准链接线程库 g -stdc20 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_semaphore10 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 对于不支持 C20 的环境Linux/Unix 系统提供了semaphore.h头文件实现了 POSIX 标准信号量常用的有命名信号量用于进程间通信和无名信号量用于线程间通信本文重点讲解线程间通信的无名信号量。 核心函数 初始化信号量 c 运行 int sem_init(sem_t *sem, int pshared, unsigned int value); 参数 1sem指向要初始化的信号量对象。 参数 2pshared是否用于进程间共享0表示仅用于线程间共享非0表示用于进程间共享。 参数 3value信号量初始值计数器初始值。 返回值成功返回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 运行 # 无需C20标准链接线程库即可 g semaphore_linux.cpp -o semaphore_linux -pthread ./semaphore_linux 运行结果说明 生产者和消费者线程有序执行生产者生产一个数据后必须等待消费者消费完毕才能继续生产消费者也只能在生产者生产后才能消费实现了完美的线程协同。 五、信号量的常见应用场景 临界区资源互斥访问使用二进制信号量替代互斥锁std::mutex实现共享资源如全局变量、文件句柄的原子操作。 资源限流使用计数信号量控制并发访问数如线程池最大并发数、接口限流、连接池最大连接数。 线程间协同实现 “生产者 - 消费者”“读者 - 写者” 等模型解决线程间的等待 / 通知问题。 进程间通信使用 Linux 命名信号量或 Windows 系统信号量实现不同进程间的同步与互斥。 六、注意事项 避免死锁申请信号量后必须保证在所有分支包括异常分支中释放信号量否则会导致其他线程永久阻塞。 C20 兼容性std::semaphore仅在 C20 及以上标准支持使用前需确认编译器版本GCC 10、Clang 11、MSVC 2019 支持 C20。 信号量与互斥锁的区别 互斥锁std::mutex只能由持有锁的线程释放信号量可以由任意线程释放。 信号量支持计数限流互斥锁仅支持单线程互斥访问。 互斥锁的性能通常优于二进制信号量简单互斥场景优先使用互斥锁。 Linux 信号量初始化无名信号量用于线程间通信时pshared参数必须设为0。 总结 信号量是由 “计数器 等待 / 唤醒机制” 组成的同步工具核心操作是 Pacquire/wait和 Vrelease/post且这两个操作均为原子操作。 信号量分为二进制信号量互斥0/1和计数信号量限流非负整数C20 提供了标准实现Linux 提供了semaphore.h兼容旧版本。 信号量可用于解决资源互斥、限流、线程协同等问题使用时需避免死锁简单互斥场景优先选择互斥锁以获得更好性能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询