C和C ++原子之间的互操作性

时间:2018-12-22 18:50:08

标签: c++ c atomic

假设我有一个可能从另一个线程取消的任务。该任务在C函数中执行,另一个线程运行C ++代码。我该怎么办?

一个粗略的例子。

C:

void do_task(atomic_bool const *cancelled);

C ++:

std::atomic_bool cancelled;
…
do_task(&cancelled);

现在,我创建了一个文件atomics.h,其内容如下:

#ifdef __cplusplus
#include <atomic>
using std::atomic_bool;
#else
#include <stdatomic.h>
#endif

它似乎有效,但是我对此没有任何保证。我想知道是否有更好(正确)的方法。

5 个答案:

答案 0 :(得分:9)

要避免所有ABI问题,您可能想实现一个C函数,该函数从C ++调用并在该atomic_bool上运行。这样,您的C ++代码无需了解有关该全局变量及其类型的任何信息:

.h文件中:

#ifdef __cplusplus
extern "C" {
#endif

void cancel_my_thread(void);
int is_my_thread_cancelled(void);

#ifdef __cplusplus
}
#endif

然后在一个.c文件中:

#include <stdatomic.h>

static atomic_bool cancelled = 0;

void cancel_my_thread(void) {
    atomic_store_explicit(&cancelled, 1, memory_order_relaxed);
}
int is_my_thread_cancelled(void) {
    return atomic_load_explicit(&cancelled, memory_order_relaxed);
}

C ++代码将包括该内容并调用cancel_my_thread

答案 1 :(得分:7)

C中的atomic_bool类型和C ++中的std::atomic<bool>类型(类型定义为std::atomic_bool)是两种无关的类型。将std::atomic_bool传递给期望C的atomic_bool的C函数是未定义行为。它的工作原理是运气与这些类型的简单定义兼容的结合。

如果C ++代码需要调用期望C的atomic_bool的C函数,则必须使用它。但是,在C ++中,<stdatomic.h>头文件does not exist是。您必须为C ++代码提供一种方法,以调用C代码以隐藏类型的方式获取所需的原子变量的指针。 (可能声明一个保存原子布尔的结构,C ++只知道该类型存在,并且只知道指向它的指针。)

答案 2 :(得分:1)

我是通过网络搜索https://developers.redhat.com/blog/2016/01/14/toward-a-better-use-of-c11-atomics-part-1/

找到的
  

跟随C ++的发展,以及描述   多线程程序的需求和语义,C11标准   通过了一套关于原子类型和操作的建议   语言。这项更改使编写便携式文件成为可能   有效处理对象的多线程软件   不可分割且没有数据争用。原子类型是完全   两种语言之间可以互操作,这样程序就可以   开发了在整个语言中共享原子类型的对象   边界。本文研究了设计的一些折衷方案,   指出了它的一些缺点,并概述了解决方案,   简化两种语言中原子对象的使用。

我现在只是在学习原子,但是看起来它在C和CPP之间是兼容的。

编辑

另一个来源Multi-Threading support in c11

答案 3 :(得分:-1)

接下来(必须)我将如何理解您的代码

// c code

void _do_task();

void do_task(volatile bool *cancelled)
{
  do {
    _do_task();
  } while (!*cancelled);
}

// c++ code

volatile bool g_cancelled;// can be modify by another thread
do_task(&cancelled);

void some_proc()
{
  //...
  g_cancelled = true;
}

我要提出一个问题-在这里我们需要将cancelled声明为atomic吗?我们这里需要原子吗?

3种情况下的原子需求:

  • 我们执行 R ead- M odify- W rite操作。说我们是否需要设置 cancelled为真,并检查是否已经true。例如,如果多个线程将cancelled设置为true,并且首先执行此操作的人需要释放一些资源,则可能需要这样做。

    if (!cancelled.exchange(true)) { free_resources(); }

  • 对类型的读取或写入操作必须是原子的。当然在 所有当前和将来所有可能的实现方式都适用 布尔类型(尽管未定义形式)。但这不是 重要。我们在这里仅检查cancelled的2个值-0(false)和 所有其他。所以即使写和读操作都在 在一个线程将非零值写入后,取消假定不是原子的 取消后,另一个线程迟早将读取修改后的非零值 来自canceled。即使它是另一个值,也不一样 第一个线程写入:例如,如果cancelled = true转换为 mov cancelled, -1; mov cancelled, 1-两个硬件,不是原子的 操作-第二个线程可以读取-1而不是最终的1true) 从取消,但如果我们仅检查非零值-全部,则不会发挥作用 另一个值中断循环-while (!*cancelled);,如果我们在此处使用原子操作进行读/写cancelled-此处没有任何变化-在一个线程原子向其写入数据后,另一个线程迟早会从中读取修改后的非零值已取消-是否进行原子操作-内存是常见的-如果一个线程(无论是原子还是否)写入内存,另一线程迟早都会查看此内存修改。

  • 我们需要将另一个已取消的读/写同步。所以我们需要 canceled周围的2个线程与内存之间的同步点 除了memory_order_relaxed以外的其他订单,例如说下一个代码:

//

void _do_task();

int result;

void do_task(atomic_bool *cancelled)
{
    do {
        _do_task();
    } while (!g_cancelled.load(memory_order_acquire));

    switch(result)
    {
    case 1:
        //...
        break;
    }
}

void some_proc()
{
    result = 1;
    g_cancelled.store(true, memory_order_release);
}

因此我们在这里而不是简单地将g_cancelled设置为true

   编写一些共享数据(result),并希望之后有另一个线程    g_cancelled的视图修改,也将是
的视图修改    共享数据(result)。但我怀疑您实际上使用/需要这个
   场景

如果这3件事都不是必需的,则这里不需要原子。您真正需要的是-一个线程仅对cancelled写入true,而另一个线程始终读取cancelled的值(而不是一次读取并缓存结果)。通常在大多数情况下,这是自动完成的,但准确来说,您需要声明取消为 volatile

但是,如果由于某种原因您确实需要原子(atomic_bool),因为您在这里跨越了语言的边界,则需要了解atomic_bool在两种语言中的具体实现,并且它们是否相同(类型声明,操作(加载,存储等)。实际上atomic_bool c c ++ 相同。

或(更好)而不是使其可见并共享类型atomic_bool,请使用

之类的接口函数

bool is_canceled(void* cancelled);

因此代码可以是下一个

// c code
void _do_task();

bool is_canceled(void* cancelled);

void do_task(void *cancelled)
{
    do {
        _do_task();
    } while (!is_canceled(cancelled));
}

// c++ code

atomic_bool g_cancelled;// can be modify by another thread

bool is_canceled(void* cancelled)
{
    return *reinterpret_cast<atomic_bool*>(cancelled);
}

void some_proc()
{
    //...
    g_cancelled = true;
}

do_task(&g_cancelled);

但是我再次怀疑在您的任务中您是否需要atomic_bool的语义。您需要volatile bool

答案 4 :(得分:-6)

操作的原子性是由硬件而不是软件引起的(好吧,在C ++中,还有一些“原子”变量仅在名称上是原子的,它们是通过互斥锁和锁实现的)。因此,基本上,C ++原子和C原子做同样的事情。因此,只要类型兼容,就不会有问题。并且使C ++ 11和C11原子类兼容。


显然,人们不了解原子和锁是如何工作的,需要进一步解释。查看当前的内存模型以获取更多信息。

1)我们将从基础开始。什么是原子?为什么是原子?记忆如何工作?

内存模型:将处理器视为几个独立的内核,每个处理器都有自己的内存(分别挂有L1,L2和L3;实际上,L3现金很普遍,但并不重要)。

为什么我们需要原子操作?

如果您不使用原子,那么每个处理器可能都有其自己的变量'x'版本,并且它们通常不同步。没有说明他们何时将使用RAM / L3现金执行同步。

使用原子操作时,将使用此类内存操作来确保与RAM / L3现金(或所需的任何内容)同步-确保不同的内核可以访问相同的变量,而不能使用其不同版本。 / p>

没人关心它是C,C ++还是您使用的任何一种语言-只要确保内存同步(读,写和修改)就永远不会有问题。

2)好吧,锁和互斥锁呢?

Mutexes倾向于与OS一起使用,并且在接下来应允许执行哪个线程的队列上排队。与原子相比,它们执行更严格的内存同步。使用原子可以根据请求/调用的函数仅同步变量本身或更多。

3)说我有atomic_bool,它可以在不同的语言(C / C ++ 11)上互换使用吗?

通常,布尔值可以通过内存操作来同步(从它们的角度来看,您只是同步一个字节的内存)。 如果编译器知道硬件可以执行此类操作,那么只要您使用标准,他们肯定会使用它们。

逻辑原子(任何std :: atomic ,其中T的大小/对齐方式错误)通过锁同步。在这种情况下,不同的语言不可能互换使用它们-如果它们使用这些锁的方法不同,或者由于某种原因一个决定使用锁,而另一种结论是它可以与原子硬件内存一起使用。同步...那么会有问题。

如果您在任何具有C / C ++的现代计算机上使用atomic_bool,它肯定可以不加锁地进行同步。

相关问题