【发布时间】:2016-11-26 14:17:44
【问题描述】:
一个原子变量在多个并发运行的线程之间共享。据我所知,线程可以读取它执行宽松负载的陈旧值:
x.load(std::memory_order_relaxed)
如果我们使用发布-获取同步会怎样?据我从文档中得知,执行获取的线程保证可以看到释放线程写入变量的内容。
// Thread 1:
x.store(33, std::memory_order_release);
// Thread 2:
x.load(std::memory_order_acquire)
在这种情况下线程 2 是否总是看到新的值?
现在,如果我们将执行宽松存储的第三个线程添加到前面的示例中,线程 2 可能看不到更新,因为仅在线程 1 和 2 之间建立了同步。我说的对吗?
最后,据说读-修改-写操作总是对新值进行操作。这是否意味着它们强制线程“看到”其他线程所做的更新,所以如果我们在读-修改-写操作之后加载,我们将看到至少与该操作一样新鲜的值?
// Thread 1:
x.fetch_add(1, std::memory_order_relaxed); // get the fresh value and add 1
if (x.load(std::memory_order_relaxed) == 3) // not older than fetch_add
fire();
// Thread 2:
x.fetch_add(2, std::memory_order_relaxed); // will see thread 1 modification
if (x.load(std::memory_order_relaxed) == 3) // will see what fetch_add has put in x or newer
fire();
对于x 最初为零,我可以确定fire 在这种情况下至少会被调用一次吗?我所有的测试都证明它可以工作,但也许这只是我的编译器或硬件的问题。
我也对订购感到好奇。 x 修改和加载之间存在明显的依赖关系,因此我认为这些指令不会被重新排序,尽管指定了宽松的顺序。还是我错了?
【问题讨论】:
-
你不知道不同线程的时序。如果线程 1 写入一个变量而线程 2 读取该变量,那么您不知道线程 2 是读取 before 还是 after 写入。 Atomic 可能有助于确保线程 2 在读取期间不会读取并获得新旧值的混合;就是这样。
-
你指的是问题的哪一部分?
-
re:最后一段:运行时和编译时的内存重新排序,以及乱序执行,都保留了单个线程的行为。所有这些东西的黄金法则(包括编译器的“as-if 规则”)是“不要破坏单线程代码”。但是,
x.fetch_add的存储部分可以在x.load之后对其他 线程全局可见。它不会在 x86 上,因为 x86 不会对稍后从同一地址加载的存储重新排序(但允许对其他地址进行 StoreLoad 重新排序)。 -
@PeterCordes 所以你的意思是 1) 不会重新排序 2) 来自线程 1 的
x.fetch_add不会对来自线程 2 的x.load可见?但是fetch_add的负载部分呢?据我所知,它应该看到新鲜的价值。只要我们在同一线程的fetch_add之后调用load,load也应该看到fetch_add放在那里的内容。另一个线程可能会在x.fetch_add和x.load之间再次启动并修改x,但在这种情况下似乎并不重要,因为该线程也会加载和检查。 -
这是个好问题。我知道答案(
fire()至少会运行一次),但我花了很长时间才写出来,因为我花了很长时间才让自己满意,我对为什么有正确的解释>(在单个线程中排序,不适用于外部观察者)。还要确保它适用于 C++,而不仅仅是像 x86 这样的强排序架构。
标签: multithreading c++11 concurrency atomic lock-free