作为我上一个问题的后续内容,atomic<T>该类使用memory_order参数指定大多数操作.与围栏相比,此内存顺序仅影响其运行的原子.据推测,通过使用几个这样的原子,你可以构建一个并发算法,其他内存的排序是不重要的.
所以我有两个问题:
我错误地认为atomic :: load也应该充当内存屏障,确保所有先前的非原子写入将被其他线程看到?
为了显示:
volatile bool arm1 = false;
std::atomic_bool arm2 = false;
bool triggered = false;
Run Code Online (Sandbox Code Playgroud)
线程1:
arm1 = true;
//std::std::atomic_thread_fence(std::memory_order_seq_cst); // this would do the trick
if (arm2.load())
triggered = true;
Run Code Online (Sandbox Code Playgroud)
线程2:
arm2.store(true);
if (arm1)
triggered = true;
Run Code Online (Sandbox Code Playgroud)
我预计在执行两个'触发'之后都是真的.请不要建议使arm1原子,重点是探索atomic :: load的行为.
虽然我不得不承认我并不完全理解内存顺序的不同松弛语义的正式定义,但我认为顺序一致的顺序非常简单,因为它保证"存在单个总顺序,其中所有线程都观察到所有修改以相同的顺序." 对我来说,这意味着std :: atomic :: load与默认内存顺序std :: memory_order_seq_cst也将充当内存栅栏."顺序一致排序"下的声明进一步证实了这一点:
总顺序排序需要在所有多核系统上使用完整的内存栅栏CPU指令.
然而,我下面的简单示例演示了MSVC 2013,gcc 4.9(x86)和clang 3.5.1(x86)的情况并非如此,其中原子载荷只是转换为加载指令.
#include <atomic>
std::atomic_long al;
#ifdef _WIN32
__declspec(noinline)
#else
__attribute__((noinline))
#endif
long load() {
return al.load(std::memory_order_seq_cst);
}
int main(int …Run Code Online (Sandbox Code Playgroud) 自C ++ 11起就知道有6个内存顺序,并且在文档中编写有关std::memory_order_acquire:
memory_order_acquire
具有此内存顺序的加载操作将在受影响的内存位置上执行获取操作:在此加载之前,无法重新排序当前线程中的任何内存访问。这样可确保在其他线程中释放相同原子变量的所有写操作在当前线程中可见。
1.非原子负载可以在atomic-acquire-load之后重新排序:
即,它不能保证非原子负载在获得原子负载后不能重新排序。
static std::atomic<int> X;
static int L;
...
void thread_func()
{
int local1 = L; // load(L)-load(X) - can be reordered with X ?
int x_local = X.load(std::memory_order_acquire); // load(X)
int local2 = L; // load(X)-load(L) - can't be reordered with X
}
Run Code Online (Sandbox Code Playgroud)
加载后int local1 = L;可以重新排序X.load(std::memory_order_acquire);吗?
2.我们可以认为非原子负载不能在atomic-acquire-load之后重新排序:
一些文章包含一幅图片,展示了获取释放语义的本质。这很容易理解,但是会引起混乱。
例如,我们可能认为std::memory_order_acquire不能对任何一系列的Load-Load操作进行重新排序,即使在atomic-acquire-load之后也无法对非atomic-load进行重新排序。
3.非原子负载可以在atomic-acquire-load之后重新排序:
澄清的好处是:Acquire语义可以防止对read-acquire进行任何以程序顺序进行的读或写操作对内存进行重新排序。http://preshing.com/20120913/acquire-and-release-semantics/
但也众所周知:在强排序的系统(x86,SPARC TSO,IBM大型机)上,大多数操作都是自动执行发布获取排序 …
从文档:
微软特定
当使用/ volatile:ms编译器选项时 - 默认情况下,当ARM以外的体系结构成为目标时 - 除了维护对其他全局对象的引用的排序之外,编译器还生成额外的代码来维护对volatile对象的引用之间的排序.特别是:
- 对volatile对象的写入(也称为volatile write)具有Release语义; 也就是说,
在写入指令
序列中的易失性对象之前发生的对全局或静态对象的引用将在编译
二进制文件中的易失性写入之前发生.- 读取volatile对象(也称为volatile读取)具有Acquire语义; 也就是说,
在读取指令
序列中的易失性存储器之后发生的对全局或静态对象的引用将在编译二进制文件中的易失性读取之后发生.这使得volatile对象可用于多线程应用程序中的内存锁定和释放.
它肯定能保证volatile阻止编译器重新编译编译时指令(因为它明确指出编译二进制文件中的指令序列是相同的).
但是众所周知,还有像硬件重新排序这样的东西(比如CPU可以自己重新排序指令).是否也volatile能阻止它?我知道同步原语(如互斥体)可以,但MS特定的volatile呢?
鉴于此处的代码:
class lazy_init
{
mutable std::once_flag flag;
mutable std::unique_ptr<expensive_data> data;
void do_init() const
{
data.reset(new expensive_data);
}
public:
expensive_data const& get_data() const
{
std::call_once(flag,&lazy_init::do_init,this);
return *data;
}
};
Run Code Online (Sandbox Code Playgroud)
我在其他地方也看到了相同模式的一些变体.所以我的问题是:为什么这段代码被认为是保存?为什么编译器在调用std :: call_once之前不能只读取数据并最终得到不正确的数据?例如
tmp = data.get();
std::call_once(flag,&lazy_init::do_init,this);
return *tmp;
Run Code Online (Sandbox Code Playgroud)
我的意思是我没有找到任何可以阻止这种情况的障碍.
我听说在处理互斥锁时,必要的内存屏障是由 pthread API 本身处理的。我想了解有关此事的更多细节。
我正在使用C11*atomics来管理几个线程之间的状态枚举.代码类似于以下内容:
static _Atomic State state;
void setToFoo(void)
{
atomic_store_explicit(&state, STATE_FOO, memory_order_release);
}
bool stateIsBar(void)
{
return atomic_load_explicit(&state, memory_order_acquire) == STATE_BAR;
}
Run Code Online (Sandbox Code Playgroud)
这组装(对于ARM Cortex-M4):
<setToFoo>:
ldr r3, [pc, #8]
dmb sy ; Memory barrier
movs r2, #0
strb r2, [r3, #0] ; store STATE_FOO
bx lr
.word 0x00000000
<stateIsBar>:
ldr r3, [pc, #16]
ldrb r0, [r3, #0] ; load state
dmb sy ; Memory barrier
sub.w r0, r0, #2 ; Comparison and return follows
clz r0, r0
lsrs r0, r0, #5 …Run Code Online (Sandbox Code Playgroud) 我最近问了几个关于原子和C++ 0x的问题,我想确保在转换任何代码之前理解排序语义.假设我们有这个前0x代码:
atomic_int a = 0;
some_struct b;
Thread A:
b = something;
atomic_store_fence();
a = 1;
Thread B:
if( a == 1 )
{
atomic_load_fence();
proc(b);
}
Run Code Online (Sandbox Code Playgroud)
使用您当前的编译器/平台为您提供的任何内容atomic_int,atomic_store_fence以及atomic_load_fence.
在C++ 0x中,代码有几种可能的形式.两个显而易见的似乎是:
atomic<int> a = ATOMIC_VAR_INIT(0);
some_struct b;
Thread A:
b = something;
atomic_thread_fence( memory_order_release );
a.store( 1, memory_order_relaxed );
Thread B:
if( a.load( memory_order_relaxed ) == 1)
{
atomic_thread_fence( memory_order_acquire );
proc(b);
}
Run Code Online (Sandbox Code Playgroud)
要么
Thread A:
b = something;
a.store( 1, memory_order_release );
Thread …Run Code Online (Sandbox Code Playgroud) 从我的研究中我了解饥饿,死锁,公平和其他并发问题的概念.然而,理论在某种程度上与实践不同,真正的工程任务往往涉及比学术等等更多的细节......
作为一名C++开发人员,我一直担心线程问题......
假设你有一个共享变量x,它指的是程序内存的一些较大部分.该变量两个线程之间共享A和B.
现在,如果我们考虑x同时从两个线程A和B线程进行读/写操作,则需要同步这些操作,对吧?因此,访问x需要某种形式的同步,这可以通过使用互斥体来实现.
现在让我们考虑另一个场景,其中x最初由线程编写A,然后传递给线程B(以某种方式),该线程只读取x.然后线程B产生对x被调用的响应y并将其传递回线程A(再次,以某种方式).我的问题是:我应该使用什么同步原语来使这个场景成为线程安全的.我读过有关原子,更重要的是内存栅栏 - 这些是我应该依赖的工具吗?
这不是存在"关键部分"的典型情况.相反,一些数据在线程之间传递,不可能在同一内存位置进行并发写入.因此,在编写之后,应首先以某种方式"刷新"数据,以便其他线程在读取之前可以看到它处于有效且一致的状态.如何在文献中称之为"可见度"?
怎么样pthread_once和它的Boost/std对应物即call_once.它帮助,如果两者x并y通过某种这是由"曾经"的功能来访问"消息队列"的线程之间传递.AFAIK它作为一种记忆围栏,但我找不到任何确认.
CPU缓存及其一致性如何?从工程角度来看,我应该知道什么?这些知识是否有助于上述场景或C++开发中常见的任何其他场景?
我知道我可能会混合很多主题,但我想更好地了解常见的工程实践是什么,以便我可以重用已知的模式.
这个问题主要与C++ 03中的情况有关,因为这是我的日常工作环境.由于我的项目主要涉及Linux,所以我可能只使用pthreads和Boost,包括Boost.Atomic.但是,如果随着C++ 11的出现,有关此类问题的任何事情都会发生变化,我也会感兴趣.
我知道问题是抽象的而不是那么精确,但任何输入都可能有用.
当我通过dispatch_async或类似的任何队列运行块时,GCD是否在块调用周围提供线程围栏?我认为它确实如此,但据我所知,文档没有提供任何方式的提示.
memory-fences ×10
c++ ×7
c++11 ×7
atomic ×4
assembly ×1
atomicity ×1
boost ×1
c11 ×1
concurrency ×1
mutex ×1
objective-c ×1
pthreads ×1
visual-c++ ×1
volatile ×1