C11 atomic_compare_exchange_strong失控:双线程为何同时进入临界区?
问题描述
我正在学习C11的atomic_compare_exchange_weak和atomic_compare_exchange_strong,编写了一段用于保护临界区的代码,但运行后发现两个线程同时进入了临界区,输出结果显示二者均成功跳出while循环。想知道这一现象的原因,以及正确实现临界区保护的方法,希望得到相关建议或资源参考。
原代码
#include <stdio.h> #include <stdlib.h> #include <stdatomic.h> #include <pthread.h> #include <unistd.h> volatile atomic_int lock = 0; void *critical_section(void *arg) { int expected = 0; int i = 500; while(i--) { while (!atomic_compare_exchange_strong(&lock, &expected, 1)); printf("Thread %ld entered the critical section, lock = %d\n", (long)arg, lock); sleep(1); printf("lock = %d\n", lock); printf("Thread %ld leaving the critical section\n", (long)arg); atomic_fetch_sub(&lock, 1); printf("lock = %d\n\n", lock); } return NULL; } int main() { pthread_t thread1, thread2; if (pthread_create(&thread1, NULL, critical_section, (void *)1L) != 0) { perror("Failed to create thread 1"); return EXIT_FAILURE; } if (pthread_create(&thread2, NULL, critical_section, (void *)2L) != 0) { perror("Failed to create thread 2"); return EXIT_FAILURE; } pthread_join(thread1, NULL); pthread_join(thread2, NULL); return EXIT_SUCCESS; }
运行结果
Thread 1 entered the critical section, lock = 1
Thread 2 entered the critical section, lock = 1
lock = 1
Thread 2 leaving the critical section
lock = 0Thread 2 entered the critical section, lock = 1
lock = 1
Thread 1 leaving the critical section
lock = 0
问题原因分析
代码存在两个核心错误,导致锁失效:
expected变量未在重试时重置atomic_compare_exchange_strong失败时,会将lock的当前值写入expected。当第一个线程获取锁后,lock变为1,第二个线程的第一次CAS失败,expected被设为1。后续循环中,线程用expected=1去匹配lock=1,CAS直接成功(将lock从1覆盖为1),导致线程错误进入临界区。锁释放方式错误
使用atomic_fetch_sub(&lock, 1)释放锁是逻辑错误:锁的状态应该是0(未持有)或1(持有),减1操作会在多线程错误进入临界区时让lock变为负数,彻底破坏锁的状态。正确的释放应该是直接将lock设为0。
正确实现代码
#include <stdio.h> #include <stdlib.h> #include <stdatomic.h> #include <pthread.h> #include <unistd.h> atomic_int lock = 0; // atomic类型自带内存可见性,无需volatile void *critical_section(void *arg) { int i = 500; while(i--) { int expected = 0; // 每次循环重置expected为锁的空闲状态值 // 自旋获取锁:CAS失败则重置expected后重试 while (!atomic_compare_exchange_strong(&lock, &expected, 1)) { expected = 0; } printf("Thread %ld entered the critical section, lock = %d\n", (long)arg, atomic_load(&lock)); sleep(1); printf("lock = %d\n", atomic_load(&lock)); printf("Thread %ld leaving the critical section\n", (long)arg); atomic_store(&lock, 0); // 直接设置为空闲状态,正确释放锁 printf("lock = %d\n\n", atomic_load(&lock)); } return NULL; } int main() { pthread_t thread1, thread2; if (pthread_create(&thread1, NULL, critical_section, (void *)1L) != 0) { perror("Failed to create thread 1"); return EXIT_FAILURE; } if (pthread_create(&thread2, NULL, critical_section, (void *)2L) != 0) { perror("Failed to create thread 2"); return EXIT_FAILURE; } pthread_join(thread1, NULL); pthread_join(thread2, NULL); return EXIT_SUCCESS; }
相关建议
- 吃透CAS操作语义:
atomic_compare_exchange_strong的核心逻辑是“比较-交换”,失败时会更新expected为当前内存值,因此每次重试前必须将expected重置为你期望的目标值(这里是锁空闲状态的0)。 - 优先使用标准互斥锁:如果不是为了学习原子操作,实际项目中推荐直接使用
pthread_mutex_t,它是经过工业级优化的同步原语,在高竞争场景下比手动实现的自旋锁更高效、可靠。 - 区分CAS的weak/strong版本:
atomic_compare_exchange_weak可能出现虚假失败(即使值匹配也返回false),必须配合循环使用;atomic_compare_exchange_strong无虚假失败,但部分平台性能略低。自旋锁场景下两者均可,weak版本在循环中可能有更好的性能表现。
内容的提问来源于stack exchange,提问作者yupe

