C++在两个不同的变量上使用memory_order_relaxed

C++ using memory_order_relaxed on two different variables

本文关键字:变量 memory relaxed order 两个 C++      更新时间:2023-10-16

在ThreadMethodOne中加载时,放松变量valA和valB同步的最正确方法是什么(假设没有错误的valA和valB缓存线共享)?我似乎不应该将ThreadMethodOne更改为使用memory_order_relaxed来加载valA,因为编译器可以在valB.load之后移动valA.load,因为一旦进行更改,valB.load上的memory_orderAcquire就不能保护valA在valB.load之后移动。我似乎也不能在valB.load上使用memory_order_relaxed,因为它将不再与ThreadMethodTwo中的fetch_add同步。交换物品并放松valA的负荷会更好吗?

这是正确的零钱吗?

nTotal += valB.load(std::memory_order_acquire);
nTotal += valA.load(std::memory_order_relaxed);

在编译器资源管理器上查看结果似乎显示,当对valA或valB使用memory_order_relaxed时,即使我不交换指令的顺序,ThreadMethodOne也会生成相同的代码。我还看到ThreadMethodTwo中的memory_order_released仍然编译为与memory_order_release相同。将memory_order_relaxed更改为以下行似乎使其成为非锁定添加"valA.store(valA.load(std::memory_order_relaxed)+1,std::memory_order_laxed);"但我不知道这样是否更好。

完整程序:

#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 %dn", 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 %dn", 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: %dn", 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: %dn", nTotalA, nTotalB);
}

清理完代码后,请参阅我的评论,我们得到了类似的内容

#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。请注意,在线程1中执行memory_order_aquire加载不会强制执行任何操作。它可以很容易地读取0的初始值。

如果线程或多或少同时运行,那么0 1的结果也是微不足道的:线程1可能会执行它的第一行,然后线程2执行它的两行,最后线程1读取线程2写入valB的值(这不是必须的,因为它是宽松的,但在这种情况下,我们只得到0 0的输出;但是,如果我们等待足够长的时间,它至少有可能读取1)。

所以,唯一感兴趣的问题是:为什么我们看不到10的输出?

原因是,如果线程1读取valA的值1,那么该值必须是线程2写入的值。这里,其值被读取的写入是写入释放,而读取本身是读取获取。这导致同步发生,导致线程2在写释放之前发生的每一个副作用在读释放之后对线程1中的每一次内存访问都可见。换句话说,如果我们读取valA==1,那么valB的后续读取(放松或不放松)将看到线程2对valB的写入,因此总是看到1,而从不看到0。

不幸的是,我不能对此说更多,因为你的问题非常不清楚:我不知道你期望或想要的结果是什么;所以我不能说要实现这一点的内存需求。