2017-10-12 114 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.

不幸的是,我不能說更多關於這個,因爲你的問題很不清楚:我不知道你期望的結果是什麼,或想成爲什麼;所以我不能說關於這種情況發生的內存要求。

相關問題