如何在C中使用原子变量?

时间:2014-08-15 01:00:51

标签: c linux atomic

我需要在C中使用原子变量,因为这个变量可以跨不同的线程访问。不想要竞争条件。

我的代码在CentOS上运行。我有什么选择?

3 个答案:

答案 0 :(得分:3)

如果您在CentOS平台上使用GCC,则可以使用__atomic built-in functions

特别感兴趣的是这个功能:

  

- 内置功能:bool __atomic_always_lock_free (size_t size, void *ptr)
  如果size字节的对象总是为目标体系结构生成无锁原子指令,则此内置函数返回true。 size必须解析为编译时常量,结果也会解析为编译时常量。

  ptr是指向可用于确定对齐的对象的可选指针。值0表示应使用典型的对齐方式。编译器也可以忽略此参数。

      if (_atomic_always_lock_free (sizeof (long long), 0))

答案 1 :(得分:2)

C11原子基元

http://en.cppreference.com/w/c/language/atomic

_Atomic const int * p1;  // p is a pointer to an atomic const int
const atomic_int * p2;   // same
const _Atomic(int) * p3; // same

在glibc 2.28中添加。通过从源代码编译glibc来测试Ubuntu 18.04(glibc 2.27):Multiple glibc libraries on a single host

示例来自:https://en.cppreference.com/w/c/language/atomic

#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>

atomic_int acnt;
int cnt;

int f(void* thr_data)
{
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
        // for this example, relaxed memory order is sufficient, e.g.
        // atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
    }
    return 0;
}

int main(void)
{
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);

    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

编译并运行:

gcc -std=c11 main.c -pthread
./a.out

可能的输出:

The atomic counter is 10000
The non-atomic counter is 8644

由于线程对非原子变量的严格访问,非原子计数器很可能小于原子计数器。

TODO:反汇编并查看++acnt;编译的内容。

答案 2 :(得分:1)

如果有人受益,我将投入两美分。原子操作是Linux中的主要问题。我一次使用gatomic.h却发现它不见了。我看到各种类型的原子选项(无论是可靠性还是可用性)都令人怀疑,而且我看到的情况一直在变化。对于O / S级别,处理器等所需的测试,它们可能会很复杂。您可以使用互斥锁-不仅复杂而且速度非常慢。

尽管在线程中可能并不理想,但这对于共享内存变量的原子操作非常有用。它很简单,并且可以在男人(或女人)已知的每个O / S和处理器以及配置上运行,可靠可靠,易于编码,并且将始终有效。

任何代码都可以用一个简单的原语-信号量使原子化。是真/假,1/0,是/否,锁定/未锁定-二进制。

建立信号量后:

set semaphore   //must be atomic

执行所有您喜欢的代码,这些代码将是原子性的,因为信号量会为您阻塞

release semaphore  //must be atomic

相对比较简单,除了“必须是原子”线。

事实证明,您可以轻松地为信号量分配一个数字(我使用了一个define,因此它们的名称类似“ #define OPEN_SEM 1”和“ #define“ CLASS_SEM 2”,依此类推。

找出最大的数字,并在程序初始化时在某个目录中打开文件(我仅出于此目的使用一个文件)。如果没有,请创建它:

if (ablockfd < 0) {         //ablockfd is static in case you want to 
                            //call it over and over           
    char *get_sy_path();                
    char lockname[100];                 

    strcpy(lockname, get_sy_path());    
    strcat(lockname, "/metlock");       
    ablockfd = open(lockname, O_RDWR);
    //error code if ablockfd bad
}

现在获取信号量:

现在使用信号量编号“锁定”长度为一个字节的文件中的“记录”。注意-该文件将永远不会真正占用磁盘空间,并且不会进行任何磁盘操作。

//sem_id is passed in and is set from OPEN_SEM or CLASS_SEM or whatever you call your semaphores.

lseek(ablockfd, sem_id, SEEK_SET); //seeks to the bytes in file of 
                                   //your semaphore number
result = lockf(ablockfd, F_LOCK, 1);   

if (result != -1) {                    
   //got the semaphore
} else {
    //failed
}

要测试信号灯是否保持:

result = lockf(ablockfd, F_TEST, 1);  //after same lseek

释放信号量:

result = lockf(ablockfd, F_ULOCK, 1);   //after same lseek

以及lockf可以执行的所有其他操作-阻止/非阻止等

注意-这比互斥锁快得多,如果进程死了(一件好事),易于编写,并且我不知道没有操作系统具有任何数量或内核数量的处理器无法自动锁定记录...如此简单的代码就可以正常工作。该文件永远不会真正存在(没有字节,只有目录),似乎对您可能拥有的文件数没有实际限制。我已经在没有简单原子解决方案的机器上使用了多年。