我目前正在努力学习使用OpenMP,我有一个问题.做这样的事情是否安全:
std::atomic<double> result;
#pragma omp parallel for
for(...)
{
result+= //some stuff;
}
Run Code Online (Sandbox Code Playgroud)
或者我应该使用:
double result;
#pragma omp parallel for
for(...)
{
double tmp=0;
//some stuff;
#pragma omp atomic
result+=tmp;
}
Run Code Online (Sandbox Code Playgroud)
谢谢 !
编辑:我知道处理的最简单的方法是使用数组,但我问,因为我很好奇
我知道,在某些情况下,您可以避免使用锁定互斥锁(std::mutex)std::atomic,从而提高性能.
你能说出这样的情况,并且最好显示一些关于如何做到这一点的示例代码(你如何使用std::atomic)?
此外,当我锁定互斥锁时,性能会下降,因为其他线程在互斥锁被锁定的一段时间内无法继续工作.这是互斥锁的唯一问题吗?我的意思是,锁定/解锁互斥锁是一项昂贵的操作,还是仅仅是我上面提到的?
我有一个环形缓冲区,如下所示:
template<class T>
class RingBuffer {
public:
bool Publish();
bool Consume(T& value);
bool IsEmpty(std::size_t head, std::size_t tail);
bool IsFull(std::size_t head, std::size_t tail);
private:
std::size_t Next(std::size_t slot);
std::vector<T> buffer_;
std::atomic<std::size_t> tail_{0};
std::atomic<std::size_t> head_{0};
static constexpr std::size_t kBufferSize{8};
};
Run Code Online (Sandbox Code Playgroud)
该数据结构旨在与两个线程一起使用:发布者线程和消费者线程。下面列出了两个不将内存顺序传递给原子的主要函数:
bool Publish(T value) {
const size_t curr_head = head_.load(/* memory order */);
const size_t curr_tail = tail_.load(/* memory_order */);
if (IsFull(curr_head, curr_tail)) {
return false;
}
buffer_[curr_tail] = std::move(value);
tail_.store(Next(curr_tail) /*, memory order */);
return true;
}
bool Consume(T& value) {
const …Run Code Online (Sandbox Code Playgroud) 正当我以为我已经掌握了原子知识时,我看到了另一篇文章。这是GCC wiki总体摘要下的摘录:
\n -Thread 1- -Thread 2- -Thread 3-\n y.store (20); if (x.load() == 10) { if (y.load() == 10)\n x.store (10); assert (y.load() == 20) assert (x.load() == 10)\n y.store (10)\n }\nRun Code Online (Sandbox Code Playgroud)\n\n\n释放/获取模式只需要所涉及的两个线程同步。这意味着同步值与其他线程不可交换。线程 2 中的断言仍然必须为 true,因为线程 1 和 2 与 x.load() 同步。线程 3 不参与此同步,因此当线程 2 和 3 使用 y.load() 同步时,线程 3 的断言可能会失败。线程 1 和 3 之间没有同步,因此不能为“x”假设任何值。
\n
文章说线程 2 中的断言不会失败,但线程 3 中的断言可能会失败。
\n我觉得这很令人惊讶。这是我的推理链,线程 3 断言不会失败\xe2\x80\x94也许有人可以告诉我哪里错了。
\ny …看起来像Xcode 5和更高版本支持C11但是当我尝试包含stdatomic.h时它说它找不到文件?是否有可能在Xcode中使用C11原子结构?
假设我的代码中包含以下全局变量:
std::atomic<uint32_t> x(...);
std::atomic<uint32_t> y(...);
std::atomic<uint32_t> z(...);
Run Code Online (Sandbox Code Playgroud)
我的任务是将x和y相乘,然后将结果存储在z中:
z = x * y
Run Code Online (Sandbox Code Playgroud)
我知道在每个对象上调用store()和load()的天真方法是完全错误的:
z.store(x.load() * y.load()); // wrong
Run Code Online (Sandbox Code Playgroud)
这样,我执行了三个单独的原子指令:另一个线程可能会滑过并同时更改其中一个值。
我可以选择比较交换(CAS)循环,但只能保证将旧值z与新值(x*y)交换时才具有原子性:我仍然不确定如何一次执行整个操作,原子步。
我也知道,包装x,y以及z一个结构内,并使其原子是不可行这里,因为结构不适合一个64位寄存器内。编译器会在后台使用锁(如果我错了,请更正我)。
这个问题只能用互斥锁解决吗?
我想知道如何才能std::atomic_ref有效地实现std::mutex非原子对象(每个对象一个),因为以下属性似乎很难实施:
相对于通过引用同一对象的任何其他atomic_ref施加的原子操作,通过atomic_ref施加到对象的原子操作是原子的。
特别是以下代码:
void set(std::vector<Big> &objs, size_t i, const Big &val) {
std::atomic_ref RefI{objs[i]};
RefI.store(val);
}
Run Code Online (Sandbox Code Playgroud)
似乎很难实现,因为std::atomic_ref每次都需要以某种方式进行选择std::mutex(除非这是由相同类型的所有对象共享的大主锁)。
我想念什么吗?还是每个对象都有责任实施std::atomic_ref,因此要么是原子的要么携带一个std::mutex?
谁能告诉我std :: atomic :: is_lock_free()是否像constexpr一样不是静态的?使它为非静态和/或为非constexpr对我来说没有意义。
论文N4455 No Sane Compiler Will Optimize Atomics讨论了编译器可以应用于原子的各种优化。在有关原子的优化部分,对于 seqlock 示例,它提到了在 LLVM 中实现的转换,其中 afetch_add(0, std::memory_order_release)变成了 amfence后跟一个普通负载,而不是通常的lock addor xadd。这个想法是这样避免了对缓存行的独占访问,并且相对便宜。将mfence仍需要无论供给,以防止序限制的StoreLoad重新排序的mov生成的指令。
无论顺序如何,都会为此类操作执行此转换read-don't-modify-write,并为fetch_add(0, memory_order_relaxed).
但是,我想知道这是否合法。C++ 标准在[atomic.order]下明确指出:
原子读-修改-写操作应始终读取在与读-修改-写操作关联的写之前写入的最后一个值(按修改顺序)。
安东尼威廉姆斯之前也注意到了有关 RMW 操作看到“最新”值的事实。
我的问题是:基于原子变量的修改顺序,基于编译器是否发出lock addvsmfence后跟普通加载,线程可以看到的值的行为是否存在差异?这种转换是否可能导致执行 RMW 操作的线程加载比最新值更旧的值?这是否违反了 C++ 内存模型的保证?
考虑一个原子读-修改-写操作,例如x.exchange(..., std::memory_order_acq_rel)。出于对其他对象的加载和存储进行排序的目的,这是否被视为:
具有获取-释放语义的单个操作?
或者,作为一个获取加载,然后是一个释放存储,附加保证其他加载和存储x将同时观察它们或两者都不观察?
如果它是 #2,那么尽管在加载之前或存储之后不能对同一线程中的其他操作进行重新排序,但仍然存在在两者之间重新排序的可能性。
作为一个具体的例子,考虑:
std::atomic<int> x, y;
void thread_A() {
x.exchange(1, std::memory_order_acq_rel);
y.store(1, std::memory_order_relaxed);
}
void thread_B() {
// These two loads cannot be reordered
int yy = y.load(std::memory_order_acquire);
int xx = x.load(std::memory_order_acquire);
std::cout << xx << ", " << yy << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
可以thread_B输出0, 1吗?
如果x.exchange()换成了x.store(1, std::memory_order_release);那么thread_B肯定能输出0, 1。是否应该exchange()排除额外的隐式负载?
cppreference听起来像 #1 是这种情况并且0, 1被禁止:
具有此内存顺序的读-修改-写操作既是获取操作又是释放操作。当前线程中的任何内存读取或写入都不能在此存储之前或之后重新排序。
但是我在标准中找不到任何明确的内容来支持这一点。实际上,该标准对原子读-修改-写操作几乎没有说明,除了 N4860 …