linux下跨进程同步必须使用命名信号量,其名字须以/开头(如/my_semaphore),通过sem_open创建并指定o_creat、权限和初始值;多个进程访问同名信号量共享同一实例;需显式sem_unlink清理,崩溃时信号量不自动恢复,c++封装应避免析构中调用sem_unlink。

Linux 下用 sem_open 创建命名信号量
跨进程同步必须用命名信号量(named semaphore),不能用 sem_init 初始化的匿名信号量——后者只在单进程内有效,且无法被其他进程访问。
关键点:命名信号量通过路径名标识,实际对应 /dev/shm/ 下的文件,所以名字必须以 / 开头,比如 /my_semaphore,不能是 my_semaphore 或 ./sem,否则 sem_open 会失败并返回 SEM_FAILED。
-
sem_open第二个参数传O_CREAT表示创建,还需补上权限掩码(如0644)和初始值(如1) - 多个进程调用相同名字的
sem_open会得到同一个信号量句柄,不是各自独立的副本 - 进程退出后信号量不会自动销毁,需显式调用
sem_unlink(通常由最后一个使用者调用)
用 sem_wait 和 sem_post 控制临界区
命名信号量的操作函数与线程版一致,但行为是跨进程的:一个进程 sem_wait 成功后,其他进程再调用会阻塞,直到有进程 sem_post。
注意:sem_wait 是阻塞调用,如果需要非阻塞,用 sem_trywait;若要带超时,得用 sem_timedwait(POSIX 标准支持,但 glibc 要求定义 _GNU_SOURCE)。
- 每次
sem_wait成功会使信号量值减 1;sem_post加 1 - 信号量初始值为 0 时,第一个
sem_wait就会阻塞,适合做“等待就绪”场景 - 不要混用
sem_post多次而没配对sem_wait,会导致计数值异常升高,失去同步意义
进程崩溃时信号量可能残留,必须处理清理逻辑
如果进程在持有信号量时崩溃(比如 segfault 或 kill -9),信号量值不会自动恢复,后续进程可能永久阻塞。这不是 bug,而是 POSIX 信号量的设计特性——它不跟踪持有者生命周期。
可靠做法不是依赖自动清理,而是提前约定清理责任:
- 让主控进程(如父进程或守护进程)负责最后调用
sem_unlink - 子进程启动时先尝试
sem_getvalue检查当前值,若为负数说明可能有残留阻塞,可结合超时重试或人工干预 - 避免在 fork 后直接继承信号量 fd —— 虽然可行,但父子进程共用同一句柄容易引发误
sem_close导致资源泄漏
C++ 封装要注意 RAII 和错误检查
直接裸用 C 接口容易漏掉 sem_close 或忽略返回值。C++ 可封装成类,但必须明确:析构函数里只调用 sem_close(释放本进程句柄),不能无脑 sem_unlink(那是全局销毁操作)。
示例关键片段:
class named_semaphore {
sem_t* sem_;
public:
named_semaphore(const char* name, unsigned int value = 1)
: sem_(sem_open(name, O_CREAT, 0644, value)) {
if (sem_ == SEM_FAILED) {
throw std::runtime_error("sem_open failed: " + std::string(strerror(errno)));
}
}
~named_semaphore() { sem_close(sem_); } // 不在这里 unlink
void wait() { if (sem_wait(sem_) == -1) throw ...; }
void post() { if (sem_post(sem_) == -1) throw ...; }
};
真正危险的是把 sem_unlink 放进析构——多个进程实例同时析构就会竞态删除,导致其他进程 sem_open 失败。
跨进程同步本身不难,难的是谁负责初始化、谁负责清理、崩溃后如何恢复——这些没法靠 API 自动解决,得靠设计约定。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











