2017-10-12 43 views
0

什么是放松对ThreadMethodOne中的加载变量valA和valB的同步的最正确方法(假设valA和valB没有错误的缓存行共享)?似乎我不应该改变ThreadMethodOne使用memory_order_relaxed来加载valA,因为编译器可能会在valB.load之后移动valA.load,因为valB.load上的memory_order_acquire不能保护valA不会在valB.load之后移动一次这一变化已经完成。它似乎也不能在valB.load上使用memory_order_relaxed,因为它不再与ThreadMethodTwo中的fetch_add同步。交换项目并放宽valA的负载会更好吗?C++在两个不同的变量上使用memory_order_relaxed

这是正确的改变吗?

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

纵观上编译器Explorer中的结果似乎显示ThreadMethodOne相同的代码生成时使用memory_order_relaxed对于任何瓦拉或值Valb,即使我不换的指令的顺序。我还看到ThreadMethodTwo中的memory_order_relaxed仍编译为与memory_order_release相同。将memory_order_relaxed更改为以下行似乎使它成为非锁定添加'valA.store(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); 
} 
+1

为什么你觉得你需要放松点餐呢? – GManNickG

+1

这更多是为了更好地理解这个话题。 –

+0

https://www.youtube.com/watch?v=c1gO9aB9nbs 关于这个问题的权威性讨论。 –

回答

0

清理你的代码后,看到我的评论,我们得到例如,

#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,如果我们等待足够长的时间)。

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

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

不幸的是,我不能说更多关于这个,因为你的问题很不清楚:我不知道你期望的结果是什么,或想成为什么;所以我不能说关于这种情况发生的内存要求。

相关问题