簡體   English   中英

C ++在兩個不同的變量上使用memory_order_relaxed

[英]C++ using memory_order_relaxed on two different variables

在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的負擔會更好嗎?

這是正確的更改嗎?

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

在valA或valB中使用memory_order_relaxed時,即使我不交換指令的順序,在Compiler Explorer上查看結果似乎也顯示了ThreadMethodOne的相同代碼生成。 我還看到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);
}

清理完代碼后,請參閱我的評論,我們得到如下信息:

#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被加入並完成了,線程2的每個變量加1,線程1的負載對此沒有影響。

如果線程1在線程2之前運行並完整完成,那么它將明顯打印0 0,而如果線程2在線程1之前運行並完整完成,則線程1將打印11。請注意如何在線程1中執行memory_order_acquire加載不執行任何操作。 它可以輕松讀取初始值0。

如果線程在同一時間或多或少地運行,那么0 1的結果也很微不足道:線程1可能執行其第一行,然后線程2執行其兩行,最后線程1讀取線程2寫入的值以valB(不必因為它是寬松的,但在這種情況下,我們只得到0 0輸出;但是,如果我們等待足夠長的時間,至少它有可能會讀為1)。

因此,唯一感興趣的問題是:為什么我們看不到1 0的輸出?

原因是,如果線程1讀取valA的值1,那么該值必須是線程2寫入的值。這里,讀取值的寫入是寫釋放,而讀取本身是讀取獲取。 這導致發生同步,從而導致線程2在寫釋放之前發生的每個副作用對於線程1在讀釋放之后在線程1中的每個內存訪問可見。 換句話說,如果我們讀取valA == 1,則隨后對valB的讀取(是否放松)將看到對線程2的valB的寫入,因此始終看到1而不是0。

不幸的是,由於您的問題還不清楚,我無法再多說些什么:我不知道您期望的結果是或希望達到什么。 因此,對於發生這種情況的內存需求,我什么也不能說。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM