You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C11 atomic_compare_exchange_strong失控:双线程为何同时进入临界区?

C11原子操作实现临界区失败:多线程同时进入临界区问题分析

问题描述

我正在学习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 = 0

Thread 2 entered the critical section, lock = 1
lock = 1
Thread 1 leaving the critical section
lock = 0

问题原因分析

代码存在两个核心错误,导致锁失效:

  1. expected变量未在重试时重置
    atomic_compare_exchange_strong失败时,会将lock的当前值写入expected。当第一个线程获取锁后,lock变为1,第二个线程的第一次CAS失败,expected被设为1。后续循环中,线程用expected=1去匹配lock=1,CAS直接成功(将lock从1覆盖为1),导致线程错误进入临界区。

  2. 锁释放方式错误
    使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:53:18