futex¶
是什么 / 解决什么问题¶
futex (Fast Userspace muTEX) 是 Linux 线程同步的基石。它将无竞争的快速路径保持在用户态(纯原子操作),只在有竞争时才陷入内核等待。pthread_mutex、pthread_cond、Go 的 sync.Mutex 底层都依赖 futex。
核心思想¶
无竞争(快速路径):
用户态 atomic_compare_exchange → 成功获取锁
不进内核!一条 CPU 指令完成。
有竞争(慢路径):
atomic CAS 失败 → futex(FUTEX_WAIT) → 内核挂起线程
释放锁时 → futex(FUTEX_WAKE) → 内核唤醒等待者
使用模式¶
简化的 mutex 实现¶
#include <linux/futex.h>
#include <sys/syscall.h>
#include <stdatomic.h>
// futex 值约定: 0=未锁, 1=已锁无等待者, 2=已锁有等待者
atomic_int lock = 0;
void mutex_lock(atomic_int *lock) {
int expected = 0;
// 快速路径:无竞争,CAS 0→1
if (atomic_compare_exchange_strong(lock, &expected, 1)) {
return; // 获取成功,没有进内核
}
// 慢路径:有竞争
while (1) {
// 标记为"有等待者"
if (atomic_exchange(lock, 2) == 0) {
return; // 在设置过程中获取了锁
}
// 内核等待:只有 *lock == 2 时才睡眠
syscall(SYS_futex, lock, FUTEX_WAIT, 2, NULL, NULL, 0);
}
}
void mutex_unlock(atomic_int *lock) {
if (atomic_fetch_sub(lock, 1) == 1) {
return; // 值从 1→0,没有等待者,不需要唤醒
}
// 有等待者(值从 2→1→设为 0)
atomic_store(lock, 0);
syscall(SYS_futex, lock, FUTEX_WAKE, 1, NULL, NULL, 0);
}
futex 系统调用¶
#include <linux/futex.h>
// 等待: 如果 *uaddr == val 则睡眠
long futex(int *uaddr, int op, int val,
const struct timespec *timeout,
int *uaddr2, int val3);
// FUTEX_WAIT: 原子检查 *uaddr == val && sleep
syscall(SYS_futex, &futex_word, FUTEX_WAIT, expected_val,
timeout, NULL, 0);
// 返回 0: 被 WAKE 唤醒
// 返回 -1, EAGAIN: *uaddr != val(值已改变,不睡眠)
// 返回 -1, ETIMEDOUT: 超时
// 返回 -1, EINTR: 被信号打断
// FUTEX_WAKE: 唤醒最多 n 个等待者
syscall(SYS_futex, &futex_word, FUTEX_WAKE, n, NULL, NULL, 0);
// 返回实际唤醒的线程数
条件变量的 futex 实现(简化)¶
// pthread_cond_wait 底层:
// 1. 释放关联的 mutex
// 2. futex(FUTEX_WAIT, &cond_seq, current_seq, ...)
// 3. 被唤醒后重新获取 mutex
// pthread_cond_signal 底层:
// 1. 增加序列号
// 2. futex(FUTEX_WAKE, &cond_seq, 1, ...)
行为与语义¶
为什么 FUTEX_WAIT 需要检查值¶
// 防止 lost wake-up 问题:
// 线程A准备 WAIT,但还没进入内核
// 线程B此时 WAKE
// 线程A进入 WAIT → 永远睡下去(错过了 WAKE)
// futex 的解决方案:
// WAIT 在内核中原子地检查 *uaddr == val
// 如果值已改变(说明有 WAKE 发生),立即返回 EAGAIN
FUTEX_PRIVATE_FLAG¶
// 如果 futex 只在同一进程的线程间使用(最常见情况):
syscall(SYS_futex, &lock, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, val, ...);
// PRIVATE 跳过了共享内存查找,性能更好
// glibc 的 pthread_mutex 默认用 PRIVATE
PI futex (Priority Inheritance)¶
// 优先级继承:解决优先级反转问题
// 低优先级线程持有锁 → 高优先级线程等锁 → 中优先级线程抢占低优先级
// 结果:高优先级被间接阻塞
// PI futex:内核临时提升持锁线程的优先级
FUTEX_LOCK_PI / FUTEX_UNLOCK_PI
// 对应 pthread:
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
性能考量¶
futex vs 自旋锁¶
| futex | spinlock | |
|---|---|---|
| 无竞争 | 1次 atomic(纳秒级) | 1次 atomic(纳秒级) |
| 有竞争 | syscall + 上下文切换(微秒级) | 忙等(浪费 CPU) |
| 适用 | 竞争可能持续较长时间 | 临界区极短 + 少竞争 |
adaptive mutex(混合策略)¶
glibc 的 PTHREAD_MUTEX_ADAPTIVE_NP:
1. 先自旋一小段时间(期望锁很快释放)
2. 自旋未获取 → 退化为 futex WAIT(避免浪费 CPU)
适合:锁持有时间通常很短但偶尔较长的场景
futex 的内核开销¶
FUTEX_WAIT: 加入哈希表等待队列 + 调度出去 ≈ 几微秒
FUTEX_WAKE: 从哈希表找到等待者 + 唤醒 ≈ 几微秒
哈希表桶冲突(多个 futex 映射到同一桶)会导致性能退化
内核用 256 个桶的哈希表管理所有 futex
常见陷阱与 FAQ¶
1. futex 地址必须对齐¶
2. 虚假唤醒 (spurious wakeup)¶
// futex WAIT 可能在没有对应 WAKE 的情况下返回
// (内核实现原因或信号中断)
// 必须在循环中检查条件
while (atomic_load(&lock) == 2) {
syscall(SYS_futex, &lock, FUTEX_WAIT, 2, NULL, NULL, 0);
// 唤醒后重新检查条件
}
3. 进程间共享 futex¶
// 跨进程的 futex 必须在共享内存上
// 且不能用 FUTEX_PRIVATE_FLAG
void *shared = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
int *futex_word = (int *)shared;
// 子进程和父进程可以通过这个 futex 同步
4. pthread_mutex 的类型选择¶
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// NORMAL: 不检查重复加锁(死锁不报错)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
// ERRORCHECK: 检查重复加锁(返回 EDEADLK)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
// RECURSIVE: 允许同一线程多次加锁(引用计数)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
观测与调试¶
# 查看进程是否阻塞在 futex 上
cat /proc/<pid>/wchan
# 输出 "futex_wait_queue" 说明在等锁
# strace 看 futex 调用
strace -e trace=futex -p <pid>
# futex(0x7f..., FUTEX_WAIT_PRIVATE, 0, NULL) = 0
# 统计 futex 等待时间
bpftrace -e '
tracepoint:syscalls:sys_enter_futex /args->op & 0xf == 0/ {
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_futex /@start[tid]/ {
@wait_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
# 检测锁竞争热点
perf lock record ./app
perf lock report
延伸阅读¶
man 2 futex— 系统调用详解man 7 futex— 概念说明- Ulrich Drepper «Futexes are Tricky» — 经典实现参考
- glibc
nptl/源码 — pthread 的实际 futex 使用