我正在尝试理解c ++ 11中的内存防护,我知道有更好的方法可以做到这一点,原子变量等等,但是想知道这种用法是否正确.我意识到这个程序没有做任何有用的事情,我只是想确保fence功能的使用做了我认为他们做的事情.
基本上,该版本确保在围栏之前在此线程中所做的任何更改对于围栏之后的其他线程可见,并且在第二个线程中对变量的任何更改在围栏之后的线程中是否可见?
我的理解是否正确?或者我完全错过了这一点?
#include <iostream>
#include <atomic>
#include <thread>
int a;
void func1()
{
for(int i = 0; i < 1000000; ++i)
{
a = i;
// Ensure that changes to a to this point are visible to other threads
atomic_thread_fence(std::memory_order_release);
}
}
void func2()
{
for(int i = 0; i < 1000000; ++i)
{
// Ensure that this thread's view of a is up to date
atomic_thread_fence(std::memory_order_acquire);
std::cout << a;
}
}
int main()
{
std::thread t1 (func1);
std::thread t2 (func2);
t1.join(); t2.join();
}
Run Code Online (Sandbox Code Playgroud)
bam*_*s53 38
您的使用实际上并不能确保您在评论中提到的内容.也就是说,您对栅栏的使用并不能确保您的分配a对其他线程可见,或者您从中读取的值a是"最新的".这是因为,尽管您似乎已经了解了应该使用围栏的基本概念,但您的代码实际上并不满足这些围栏"同步"的确切要求.
这是一个不同的例子,我认为更好地证明了正确的用法.
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<bool> flag(false);
int a;
void func1()
{
a = 100;
atomic_thread_fence(std::memory_order_release);
flag.store(true, std::memory_order_relaxed);
}
void func2()
{
while(!flag.load(std::memory_order_relaxed))
;
atomic_thread_fence(std::memory_order_acquire);
std::cout << a << '\n'; // guaranteed to print 100
}
int main()
{
std::thread t1 (func1);
std::thread t2 (func2);
t1.join(); t2.join();
}
Run Code Online (Sandbox Code Playgroud)
原子标志上的加载和存储不同步,因为它们都使用宽松的内存排序.如果没有围栏,这段代码就会成为数据竞争对手,因为我们在不同的线程中执行非原子对象的冲突操作,并且没有围栏和它们提供的同步,冲突操作之间就不会发生冲突a.
然而,我们确保线程2将读取线程1写入的标志(因为我们循环直到看到该值),并且因为原子写入发生在释放栅栏和原子读取发生之后 - 在获得围栏之前,围栏同步.(具体要求见§29.8/ 2.)
这种同步意味着在释放围栏发生之前发生的任何事情 - 在发生任何事情之前 - 在获取围栏之后.因此,在非a原子读取之前发生非原子写入a.
当你在循环中编写一个变量时,事情会变得棘手,因为你可能会为某个特定的迭代建立一个先发生的关系,而不是其他的迭代,导致数据竞争.
std::atomic<int> f(0);
int a;
void func1()
{
for (int i = 0; i<1000000; ++i) {
a = i;
atomic_thread_fence(std::memory_order_release);
f.store(i, std::memory_order_relaxed);
}
}
void func2()
{
int prev_value = 0;
while (prev_value < 1000000) {
while (true) {
int new_val = f.load(std::memory_order_relaxed);
if (prev_val < new_val) {
prev_val = new_val;
break;
}
}
atomic_thread_fence(std::memory_order_acquire);
std::cout << a << '\n';
}
}
Run Code Online (Sandbox Code Playgroud)
此代码仍会导致围栏同步,但不会消除数据争用.例如,如果f.load()碰巧返回10,那么我们就知道了a=1,a=2... a=10已经发生了 - 在那个特定之前cout<<a,但我们不知道cout<<a发生过 - 之前a=11.这些是在不同线程上的冲突操作,没有发生之前的关系; 数据竞赛.
您的使用是正确的,但不足以保证任何有用的东西.
例如,如果编译器想要执行a = i;以下操作,则可以像这样在内部实现:
while(a != i)
{
++a;
atomic_thread_fence(std::memory_order_release);
}
Run Code Online (Sandbox Code Playgroud)
所以其他线程可能会看到任何值.
当然,编译器永远不会实现这样的简单赋值.但是,有些情况下,类似的复杂行为实际上是一种优化,因此依赖于以任何特定方式在内部实现的普通代码是一个非常糟糕的主意.这就是为什么我们有像原子操作和栅栏这样的东西只有在与这些操作一起使用时才会产生有保证的结
| 归档时间: |
|
| 查看次数: |
23757 次 |
| 最近记录: |