基于futex实现信号量时futex_wake返回0的问题排查
问题分析与解决方案
首先,你遇到的futex_wake返回0导致断言失败的问题,核心原因是你错误地假设了调用futex_wake时一定存在等待的线程,但实际上即使信号量的value在sem_post前小于等于0,也可能没有线程在futex_wait上阻塞,这是完全合法的场景。
为什么会出现这种情况?
举个典型的竞态场景:
- 线程A调用
sem_wait,原子性地将信号量value从0减到-1,此时它准备调用futex_wait进入等待。 - 就在线程A执行
futex_wait之前,线程B调用了sem_post,原子性地将value从-1加到0。因为之前的value是-1,线程B会触发futex_wake。 - 但此时线程A还没真正进入
futex_wait的阻塞状态,所以futex_wake找不到等待的线程,返回0。 - 之后线程A执行
futex_wait,但此时value已经是0,futex_wait会因为当前值不等于预期的-1而直接返回,不会阻塞——这其实是正确的逻辑,但你的断言会因为futex_wake返回0而触发失败。
POSIX信号量的sem_post不会暴露唤醒线程的数量,所以你看不到这个情况,但它的内部实现同样允许这种场景存在,只是没有断言而已。
修复方案
- 立即移除
sem_post中对futex_wake返回值为1的断言:futex_wake的返回值是实际被唤醒的线程数,返回0是完全合法的,不代表你的实现有错误。 - 检查
sem_wait的实现是否正确处理了竞态和虚假唤醒:正确的sem_wait需要用循环来确保最终能获取到信号量,比如:
typedef struct { int value; } sem_t; void sem_wait(sem_t* sem) { int prev = __atomic_sub_fetch(&sem->value, 1, __ATOMIC_SEQ_CST); if (prev > 0) { return; } // 循环处理虚假唤醒或竞态场景 while (__atomic_load_n(&sem->value, __ATOMIC_SEQ_CST) <= 0) { int curr = __atomic_load_n(&sem->value, __ATOMIC_SEQ_CST); // 若信号量用于跨进程共享,需替换为FUTEX_WAIT syscall(SYS_futex, &sem->value, FUTEX_WAIT_PRIVATE, curr, NULL, NULL, 0); } } void sem_post(sem_t* sem) { int prev = __atomic_add_fetch(&sem->value, 1, __ATOMIC_SEQ_CST); // 只有之前的value小于0时,才可能有线程在等待 if (prev < 0) { // 若信号量用于跨进程共享,需替换为FUTEX_WAKE syscall(SYS_futex, &sem->value, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0); } }
额外注意事项
- 如果你是在跨进程共享内存中使用信号量,要把
FUTEX_WAIT_PRIVATE和FUTEX_WAKE_PRIVATE换成FUTEX_WAIT和FUTEX_WAKE,PRIVATE版本只适用于同一进程内的线程。 - 确保原子操作的内存序正确(比如
__ATOMIC_SEQ_CST或者合适的ACQUIRE/RELEASE),否则可能出现内存可见性问题,导致线程看不到信号量的更新。
内容的提问来源于stack exchange,提问作者zhao
相关产品推荐
相关产品推荐

