std::memory_order_seq_cst 的工作原理
How std::memory_order_seq_cst works
我以 std::memory_order_seq_cst 为例:
http://en.cppreference.com/w/cpp/atomic/memory_order
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_seq_cst);
}
void write_y()
{
y.store(true, std::memory_order_seq_cst);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_seq_cst))
;
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
int main()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // will never happen
}
Acquire/Release versus Sequentially Consistent memory order的问题中也提到了这个例子。
我的问题是线程 c 和线程 d 怎么可能看到不同的东西?如果可能的话,为什么下面这个简单的例子总是屈服于 z=3?例如,线程 b 可以说 "okay I see 0 even though thread a is already done so z becomes 0+1 again"
#include <atomic>
#include <iostream>
std::atomic<int> z = {0};
void increment()
{
z.fetch_add(1, std::memory_order_relaxed);
}
int main()
{
std::thread a(increment);
std::thread b(increment);
std::thread c(increment);
a.join(); b.join(); c.join();
std::cout << z.load() << '\n';
}
因为read-modify-write操作有特殊的保证。
根据标准[atomics.order] paragraph 11:
Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.
因此,通过在您的评论中看到不同的内容,您的意思是 线程 C 看到 x==1,y==0 并且线程 D 看到 x==0 和 y==1.顺序一致性可能吗?
让我们假设这个总顺序(修改是这个符号化记忆状态之间的转换):
{x==0,y==0} : S0
{x==1,y==0} : S1
{x==1,y==1} : S2
当我们说 "see" 时,我们的意思是线程可能执行负载。两个加载不能在一个线程中同时执行。那么线程 C 怎么可能看到 x==1 then 看到 y==0 而线程 D 看到 x==0 then 看到 y ==1?线程 C 在内存处于状态 S1 时执行这两个加载,线程 D 在状态 S0 看到 x
,然后在状态 S2 看到 y
。
在您的示例代码中,线程 C 加载 x 然后加载 y,线程 D 重复加载 y 直到它为真,然后加载 x。所以在y==1之后,保证在这个总序中x==1
。
正如 Minee 在其评论中所说,如果使用顺序一致性内存顺序代替顺序一致性内存顺序,则不会有任何期望 acquire/release 内存顺序:acquire/release 语义并不意味着任何总顺序,此外在 x
的商店和 y
的商店之间没有 在 之前发生的关系。所以断言 z.load()!=0
可能会触发。
My question is how it is possible that thread c and thread d see
different things?
理论上是允许的,如果您有多个原子变量并且某些操作没有 memory_order_seq_cst
排序,那么在实践中它可能会发生。
所以在您的代码中不可能在所有操作上使用 memory_order_seq_cst
(仅在某些操作上使用它是危险的,因为它会导致细微的错误) .
For instance, thread b could say "okay I see 0 even though thread a is
already done so z becomes 0+1 again"
没有
但在任何情况下,单个原子变量上允许的内容与内存排序无关,这会影响其余内存的可见性,并且不会影响其上的对象你正在经营.
如果您只有一个原子变量而没有其他共享状态,则可见性无关紧要,因为没有任何内容可以显示。
[标准说明注意事项:
该标准暗示至少在理论上,该断言并非在所有情况下都适用于宽松的操作。但是线程上的标准是疯狂的:除非我的断言是真的,否则它定义不正确。
无论如何,该标准表示在实践中,实现应避免允许在我的断言为假的情况下执行。在实践中,它们不会随时随地发生。]
我以 std::memory_order_seq_cst 为例: http://en.cppreference.com/w/cpp/atomic/memory_order
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_seq_cst);
}
void write_y()
{
y.store(true, std::memory_order_seq_cst);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_seq_cst))
;
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
int main()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // will never happen
}
Acquire/Release versus Sequentially Consistent memory order的问题中也提到了这个例子。
我的问题是线程 c 和线程 d 怎么可能看到不同的东西?如果可能的话,为什么下面这个简单的例子总是屈服于 z=3?例如,线程 b 可以说 "okay I see 0 even though thread a is already done so z becomes 0+1 again"
#include <atomic>
#include <iostream>
std::atomic<int> z = {0};
void increment()
{
z.fetch_add(1, std::memory_order_relaxed);
}
int main()
{
std::thread a(increment);
std::thread b(increment);
std::thread c(increment);
a.join(); b.join(); c.join();
std::cout << z.load() << '\n';
}
因为read-modify-write操作有特殊的保证。
根据标准[atomics.order] paragraph 11:
Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.
因此,通过在您的评论中看到不同的内容,您的意思是 线程 C 看到 x==1,y==0 并且线程 D 看到 x==0 和 y==1.顺序一致性可能吗?
让我们假设这个总顺序(修改是这个符号化记忆状态之间的转换):
{x==0,y==0} : S0
{x==1,y==0} : S1
{x==1,y==1} : S2
当我们说 "see" 时,我们的意思是线程可能执行负载。两个加载不能在一个线程中同时执行。那么线程 C 怎么可能看到 x==1 then 看到 y==0 而线程 D 看到 x==0 then 看到 y ==1?线程 C 在内存处于状态 S1 时执行这两个加载,线程 D 在状态 S0 看到 x
,然后在状态 S2 看到 y
。
在您的示例代码中,线程 C 加载 x 然后加载 y,线程 D 重复加载 y 直到它为真,然后加载 x。所以在y==1之后,保证在这个总序中x==1
。
正如 Minee 在其评论中所说,如果使用顺序一致性内存顺序代替顺序一致性内存顺序,则不会有任何期望 acquire/release 内存顺序:acquire/release 语义并不意味着任何总顺序,此外在 x
的商店和 y
的商店之间没有 在 之前发生的关系。所以断言 z.load()!=0
可能会触发。
My question is how it is possible that thread c and thread d see different things?
理论上是允许的,如果您有多个原子变量并且某些操作没有 memory_order_seq_cst
排序,那么在实践中它可能会发生。
所以在您的代码中不可能在所有操作上使用 memory_order_seq_cst
(仅在某些操作上使用它是危险的,因为它会导致细微的错误) .
For instance, thread b could say "okay I see 0 even though thread a is already done so z becomes 0+1 again"
没有
但在任何情况下,单个原子变量上允许的内容与内存排序无关,这会影响其余内存的可见性,并且不会影响其上的对象你正在经营.
如果您只有一个原子变量而没有其他共享状态,则可见性无关紧要,因为没有任何内容可以显示。
[标准说明注意事项:
该标准暗示至少在理论上,该断言并非在所有情况下都适用于宽松的操作。但是线程上的标准是疯狂的:除非我的断言是真的,否则它定义不正确。
无论如何,该标准表示在实践中,实现应避免允许在我的断言为假的情况下执行。在实践中,它们不会随时随地发生。]