在ThreadMethodOne中放宽变量valA和valB同步的最正确方法是什么(假设没有错误的高速缓存共享valA和valB)?似乎我不应该更改ThreadMethodOne以使用memory_order_relaxed来加载valA,因为编译器可以在valB.load之后移动valA.load,因为valB.load上的memory_order_acquire不保护valA.load之后的valA移动做出改变。似乎我不能在valB.load上使用memory_order_relaxed,因为它不再与ThreadMethodTwo中的fetch_add同步。交换物品并放松valA的负担会更好吗?
这是正确的改变吗?
nTotal += valB.load(std::memory_order_acquire);
nTotal += valA.load(std::memory_order_relaxed);
查看Compiler Explorer上的结果似乎表明,当使用memory_order_relaxed进行valA或valB时,ThreadMethodOne的代码生成相同,即使我不交换指令的顺序。我还看到ThreadMethodTwo中的memory_order_relaxed仍然编译为与memory_order_release相同。将memory_order_relaxed更改为以下行似乎使其成为非锁定添加&#val ;.(valA.load(std :: memory_order_relaxed)+ 1,std :: memory_order_relaxed);'但我不知道这是否更好。
完整计划:
#include <stdio.h>
#include <stdlib.h>
#include <thread>
#include <atomic>
#include <unistd.h>
bool bDone { false };
std::atomic_int valA {0};
std::atomic_int valB {0};
void ThreadMethodOne()
{
while (!bDone)
{
int nTotal {0};
nTotal += valA.load(std::memory_order_acquire);
nTotal += valB.load(std::memory_order_acquire);
printf("Thread total %d\n", nTotal);
}
}
void ThreadMethodTwo()
{
while (!bDone)
{
valA.fetch_add(1, std::memory_order_relaxed);
valB.fetch_add(1, std::memory_order_release);
}
}
int main()
{
std::thread tOne(ThreadMethodOne);
std::thread tTwo(ThreadMethodTwo);
usleep(100000);
bDone = true;
tOne.join();
tTwo.join();
int nTotal = valA.load(std::memory_order_acquire);
nTotal += valB.load(std::memory_order_acquire);
printf("Completed total %d\n", nTotal);
}
更好的样本离开原始样本,因为它是在评论中写的那个
#include <stdio.h>
#include <stdlib.h>
#include <thread>
#include <atomic>
#include <unistd.h>
std::atomic_bool bDone { false };
std::atomic_int valA {0};
std::atomic_int valB {0};
void ThreadMethodOne()
{
while (!bDone)
{
int nTotalA = valA.load(std::memory_order_acquire);
int nTotalB = valB.load(std::memory_order_relaxed);
printf("Thread total A: %d B: %d\n", nTotalA, nTotalB);
}
}
void ThreadMethodTwo()
{
while (!bDone)
{
valB.fetch_add(1, std::memory_order_relaxed);
valA.fetch_add(1, std::memory_order_release);
}
}
int main()
{
std::thread tOne(ThreadMethodOne);
std::thread tTwo(ThreadMethodTwo);
usleep(100000);
bDone = true;
tOne.join();
tTwo.join();
int nTotalA = valA.load(std::memory_order_acquire);
int nTotalB = valB.load(std::memory_order_relaxed);
printf("Completed total A: %d B: %d\n", nTotalA, nTotalB);
}
答案 0 :(得分:1)
清理完代码后,请参阅我的评论,我们会得到类似的内容,
#include <atomic>
#include <iostream>
std::atomic_int valA {0};
std::atomic_int valB {0};
void ThreadMethodOne()
{
int nTotalA = valA.load(std::memory_order_acquire);
int nTotalB = valB.load(std::memory_order_relaxed);
std::cout << "Thread total A: " << nTotalA << " B: " << nTotalB << '\n';
}
void ThreadMethodTwo()
{
valB.fetch_add(1, std::memory_order_relaxed);
valA.fetch_add(1, std::memory_order_release);
}
int main()
{
std::thread tOne(ThreadMethodOne);
std::thread tTwo(ThreadMethodTwo);
tOne.join();
tTwo.join();
int nTotalA = valA.load(std::memory_order_acquire);
int nTotalB = valB.load(std::memory_order_relaxed);
std::cout << "Completed total A: " << nTotalA << " B: " << nTotalB << '\n';
}
该计划的可能结果是:
Thread total A: 0 B: 0
Completed total A: 1 B: 1
或
Thread total A: 0 B: 1
Completed total A: 1 B: 1
或
Thread total A: 1 B: 1
Completed total A: 1 B: 1
它始终打印Completed total A: 1 B: 1
的原因是线程2已连接并因此完成,每个变量加1,而线程1中的加载对此没有影响。
如果线程1在线程2之前运行并完整地完成,那么它显然将打印0 0,而如果线程2在线程1之前运行并完全完成,则线程1将打印1 1.注意如何执行memory_order_acquire加载在线程1中没有强制执行任何操作。它可以很容易地读取0的初始值。
如果线程同时或多或少地运行,则0 1的结果也非常简单:线程1可能执行其第一行,然后线程2执行其两行,最后线程1读取由线程2到valB(它不必因为它是放松的,但在这种情况下我们只得到0 0输出;但至少它可能会读取1,如果我们等待足够长的时间)
因此,唯一感兴趣的问题是:为什么我们看不到输出为0?
原因是如果线程1为valA读取值1,则必须是线程2写入的值。这里读取其值的写入是写入释放,而读取本身是读取获取。这会导致同步发生,导致在释放之前发生的线程2的每个副作用在读取释放后对线程1中的每个内存访问都可见。换句话说,如果我们读取valA == 1那么后续读取valB(放宽或不放松)将看到写入线程2的valB,因此总是看到1而从不看到0。
不幸的是我不能多说这个,因为你的问题很不清楚:我不知道你对结果的期望是什么,或者想要成为什么样的结果;所以我无法对内存要求说些什么。