Futex与Pthreads锁实现异常:sum计算结果不符预期求助
问题:Futex实现的锁导致线程同步错误,结果不符合预期
两个线程分别对全局变量sum执行1000000次加1和减1操作,预期最终sum值为0,但实际运行时结果时而正确时而错误,问题出在自定义的futex锁实现上。
原代码
#include <stdio.h> #include <pthread.h> #include <stdint.h> #include <stdatomic.h> #include <sys/syscall.h> #include <linux/futex.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> int64_t sum; pthread_mutex_t mtx; uint32_t ftx; int futex(uint32_t *uaddr, int futex_op, uint32_t val, const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) { return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); } void fwait(uint32_t *futx) { long st; uint32_t one = 1; do { if (atomic_compare_exchange_strong(futx, &one, 0)) break; st = futex(futx, FUTEX_WAIT_PRIVATE, 0, NULL, NULL, 0); if ((st == -1) && (errno != EAGAIN)) { printf("error-wait-futex\n"); exit(1); } } while (1); } void fwake(uint32_t *futx) { long st; uint32_t zero = 0; if (atomic_compare_exchange_strong(futx, &zero, 1)) { st = futex(futx, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0); if (st == -1) { printf("error-wake-futex\n"); exit(1); } } } void lock(void) { fwait(&ftx); } void unlock(void) { fwake(&ftx); } void * t1_handler(void *arg) { int mod; uint64_t x; mod = *(int *)arg; x = 0; while (x < 1000000) { lock(); sum += mod; x++; unlock(); } } void proc(void) { pthread_t t1, t2; int a1, a2; sum = 0; ftx = 1; a1 = 1; a2 = -1; pthread_mutex_init(&mtx, NULL); pthread_create(&t1, NULL, t1_handler, &a1); pthread_create(&t2, NULL, t1_handler, &a2); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("sum: %lld\n", sum); } int main(void) { proc(); return 0; }
错误原因分析
1. fwait函数的CAS逻辑错误
atomic_compare_exchange_strong的行为是:如果目标地址的值等于预期值,就将其替换为目标值并返回true;否则,会把预期变量的值更新为目标地址的当前值并返回false。
原代码中one变量只在函数开头初始化一次为1,当第一次CAS失败(此时ftx为0),one会被自动修改为0。后续循环中,CAS操作变成了尝试将ftx从0改为0——这必然成功,导致线程无需等待直接进入临界区,多个线程同时操作sum,引发竞态条件,最终结果错误。
2. fwake函数的解锁逻辑冗余且存在风险
只有持有锁的线程才会调用unlock,此时ftx必然是0(获取锁时会将其设为0),不需要用atomic_compare_exchange_strong来检查状态。如果CAS失败(理论上不该发生,但极端情况可能触发),会跳过唤醒操作,导致等待线程永远阻塞。
修复后的代码
#include <stdio.h> #include <pthread.h> #include <stdint.h> #include <stdatomic.h> #include <sys/syscall.h> #include <linux/futex.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> int64_t sum; pthread_mutex_t mtx; uint32_t ftx; int futex(uint32_t *uaddr, int futex_op, uint32_t val, const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) { return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); } void fwait(uint32_t *futx) { long st; uint32_t one; do { one = 1; // 每次循环重置预期值,避免被CAS修改后逻辑混乱 if (atomic_compare_exchange_strong(futx, &one, 0)) break; st = futex(futx, FUTEX_WAIT_PRIVATE, 0, NULL, NULL, 0); if ((st == -1) && (errno != EAGAIN)) { printf("error-wait-futex\n"); exit(1); } } while (1); } void fwake(uint32_t *futx) { long st; atomic_store(futx, 1); // 直接将锁设为可用状态,无需CAS检查 st = futex(futx, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0); if (st == -1) { printf("error-wake-futex\n"); exit(1); } } void lock(void) { fwait(&ftx); } void unlock(void) { fwake(&ftx); } void * t1_handler(void *arg) { int mod; uint64_t x; mod = *(int *)arg; x = 0; while (x < 1000000) { lock(); sum += mod; x++; unlock(); } } void proc(void) { pthread_t t1, t2; int a1, a2; sum = 0; ftx = 1; a1 = 1; a2 = -1; pthread_mutex_init(&mtx, NULL); pthread_create(&t1, NULL, t1_handler, &a1); pthread_create(&t2, NULL, t1_handler, &a2); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("sum: %lld\n", sum); } int main(void) { proc(); return 0; }
内容的提问来源于stack exchange,提问作者user2699113
相关产品推荐
相关产品推荐

