假设我有一个可能从另一个线程取消的任务。该任务在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
它似乎有效,但是我对此没有任何保证。我想知道是否有更好(正确)的方法。
答案 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之间是兼容的。
编辑
答案 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
而不是最终的1
(true
)
从取消,但如果我们仅检查非零值-全部,则不会发挥作用
另一个值中断循环-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
如果您在任何具有C / C ++的现代计算机上使用atomic_bool,它肯定可以不加锁地进行同步。