posix信号量(sem_t)在linux线程同步中轻量灵活,需用sem_init初始化并检查返回值,配对使用sem_wait/sem_post,最后sem_destroy;c++20支持std::counting_semaphore但不跨进程、无超时接口。

Linux下用sem_wait和sem_post实现线程间同步
POSIX信号量(sem_t)是C++在Linux环境下做线程同步的轻量级选择,比互斥锁更灵活,适合控制资源数量或协调多个线程的执行节奏。它不依赖C++标准库,需链接-pthread,头文件为<semaphore.h></semaphore.h>。
常见错误是忘记初始化或未检查返回值:sem_init失败时返回-1,但很多人直接忽略,导致后续sem_wait阻塞或崩溃。
-
sem_init(&sem, 0, 1):第二个参数为0表示线程内共享(同一进程的多个线程可用),非0则用于进程间共享(需配合mmap等) - 初始化后务必检查返回值:
if (sem_init(&sem, 0, 1) != 0) { perror("sem_init"); } - 每个
sem_wait必须配对sem_post,否则信号量值持续减少,最终卡死 - 使用完记得
sem_destroy(&sem),否则可能泄漏内核资源
C++11及以上能否直接用std::counting_semaphore?
可以,但仅限C++20且编译器支持完整特性(GCC 11+、Clang 13+、MSVC 19.30+)。它本质是封装好的用户态计数信号量,不依赖系统调用,性能更好,也无需手动销毁。
容易踩的坑是误以为它是“万能替代”——它不支持跨进程、不能等待超时(try_acquire_for需C++2a TS补丁,主流标准未纳入),且构造时最大值受限于std::numeric_limits<ptrdiff_t>::max()</ptrdiff_t>。
- 声明:
std::counting_semaphore sem{2};表示初始值为2、最大值也为2 - 阻塞等待:
sem.acquire();;非阻塞尝试:if (sem.try_acquire()) { ... } - 注意:
acquire()可能被中断(如收到信号),但不会抛异常,需自行处理中断逻辑 - 没有
timed_acquire标准接口,要超时必须自己结合std::chrono轮询try_acquire
为什么sem_wait在多线程中有时不阻塞?
根本原因是信号量当前值大于0,sem_wait会立即递减并返回,不挂起线程。这不是bug,而是设计行为——信号量本质是“资源计数器”,只要还有资源就允许通行。
典型误用场景:想用信号量做“条件触发”,却忘了初始化值设为0,结果所有线程一上来就通过,完全失去同步效果。
- 做“启动门控”(比如等所有线程就绪再一起开始):初始化为0,由主线程
sem_post多次唤醒 - 做“资源池”(比如限制最多3个线程访问数据库):初始化为3,每次访问前
sem_wait,结束后sem_post - 调试时可用
sem_getvalue(&sem, &val)读取当前值(注意该函数不保证原子性,仅用于诊断)
Windows平台怎么用信号量?
Windows没有POSIX信号量,得用Win32 API的CreateSemaphore和WaitForSingleObject。接口风格完全不同,且句柄需显式关闭。
最常被忽略的是ReleaseSemaphore的第三个参数——它输出的是“之前信号量的值”,不是新值,很多代码误把它当返回值判断是否成功。
- 创建:
HANDLE sem = CreateSemaphore(nullptr, 1, 1, nullptr);(初始值=1,最大值=1) - 等待:
WaitForSingleObject(sem, INFINITE);或指定毫秒数(如1000) - 释放:
ReleaseSemaphore(sem, 1, nullptr);—— 第二个参数是增加量,不是目标值 - 必须配对
CloseHandle(sem),否则句柄泄漏 - 跨进程使用时需命名,并注意SECURITY_ATTRIBUTES权限设置
跨平台项目别硬套POSIX语义,Windows信号量没有“尝试获取不阻塞”的原子操作(WaitForSingleObject加0超时可模拟,但不如POSIX的sem_trywait简洁)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











