如何在C语言中进行原子递增和获取?

17

我想寻找一种方式来原子地增加一个short类型的值,并返回该值。我需要在内核模式和用户模式下都这样做,因此代码是基于Linux系统,在Intel 32位架构下编写的C语言。不幸的是,由于速度要求,使用互斥锁并不是一个好的选择。

是否有其他方法可以实现这个目标?目前看来唯一可行的选择是嵌入一些汇编代码。如果是这种情况,能否给我指出适当的汇编指令?

2个回答

22

GCC __atomic_* 内建函数

从GCC 4.8开始,__sync 内建函数已被弃用,改为使用 __atomic 内建函数: https://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/_005f_005fatomic-Builtins.html

它们实现了C++的内存模型,并被std::atomic在内部使用。

以下POSIX线程示例在x86-64上使用++会一直失败,而使用_atomic_fetch_add则始终成功。

main.c

#include <assert.h>
#include <pthread.h>
#include <stdlib.h>

enum CONSTANTS {
    NUM_THREADS = 1000,
    NUM_ITERS = 1000
};

int global = 0;

void* main_thread(void *arg) {
    int i;
    for (i = 0; i < NUM_ITERS; ++i) {
        __atomic_fetch_add(&global, 1, __ATOMIC_SEQ_CST);
        /* This fails consistently. */
        /*global++*/;
    }
    return NULL;
}

int main(void) {
    int i;
    pthread_t threads[NUM_THREADS];
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_create(&threads[i], NULL, main_thread, NULL);
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_join(threads[i], NULL); 
    assert(global == NUM_THREADS * NUM_ITERS);
    return EXIT_SUCCESS;
}

编译并运行:

gcc -std=c99 -Wall -Wextra -pedantic -o main.out ./main.c -pthread
./main.out

如何在纯C中启动线程?的页面上进行反汇编分析。

在Ubuntu18.10、GCC8.2.0、GLIBC2.28上进行测试。

C11 _Atomic

在5.1中,以上代码适用于:

_Atomic int global = 0;
global++;

C11 threads.h在glibc 2.28中被添加,它允许您在纯ANSI C中创建线程而无需使用POSIX。最小可运行示例:How do I start threads in plain C?


8

1
特别是,__sync_add_and_fetch听起来像OP想要的。 - caf
我之前找到过这些,但是有两个问题。第一个问题是我的理解是它们不能与内核编译器一起使用...我不确定为什么,所以可能有一个简单的解决方法。其次,即使在用户空间,链接器也会给我以下错误:undefined reference to `__sync_add_and_fetch_2'如果有人能指出这两个问题(或两个问题都能解决!),我将非常感激。 - Bryan
就更新而言,如果在编译时包含 -march=pentium 标志,__sync_add_and_fetch 在用户空间中确实有效(https://dev59.com/a3VC5IYBdhLWcg3w-WSs),所以现在我只是在努力让它在内核模块中正常工作。 - Bryan
另一个更新,我认为这是因为函数期望一个 int,而我使用(并被限制为)一个 short。 - Bryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接