簡體   English   中英

一個線程寫入和另一個非原子讀取保證寫入

[英]Is write guaranteed with one thread writing and another reading non-atomic

說我有

bool unsafeBool = false;

int main()
{
    std::thread reader = std::thread([](){ 
        std::this_thread::sleep_for(1ns);
        if(unsafeBool) 
            std::cout << "unsafe bool is true" << std::endl; 
    });
    std::thread writer = std::thread([](){ 
        unsafeBool = true; 
    });
    reader.join();
    writer.join();
}

是否保證在unsafeBool完成后unsafeBool變為true 我知道讀取器輸出的是未定義的行為,但據我所知,寫入應該沒問題。

writer.join()它保證unsafeBool == true 但是在閱讀器線程中,對它的訪問是一場數據競賽。

UB 是並且仍然是 UB,可以推理為什么事情會以它們發生的方式發生,但是,您不能依賴於此。

您有競爭條件,請通過以下任一方式修復它:添加鎖或將類型更改為原子。

由於您的代碼中有 UB,您的編譯器可以假設這不會發生。 如果它可以檢測到這一點,它可以將您的完整函數更改為 noop,因為它永遠不會在有效程序中被調用。

如果不這樣做,則行為將取決於您的處理器和鏈接到它的緩存。 在那里,如果連接之后的代碼使用與讀取布爾值的線程相同的核心(連接之前),您甚至可能在那里仍然有 false 而無需使緩存無效。

在實踐中,使用 Intel X86 處理器,您不會看到競爭條件帶來的很多副作用,因為它已使寫入時的緩存無效。

一些實現保證任何嘗試讀取一個字大小或更小的對象的值,該對象在它更改時沒有限定為volatile將產生一個舊值或新值,任意選擇。 在這種保證有用的情況下,編譯器始終如一地維護它的成本通常低於解決其缺失的成本(除其他外,因為程序員可以解決其缺失的任何方式都會限制編譯器的在舊值或新值之間進行選擇的自由)。

然而,在其他一些實現中,即使看起來應該涉及對值的單次讀取的操作也可能產生組合多次讀取結果的代碼。 當使用命令行參數-xc -O2 -mcpu=cortex-m0調用 ARM gcc 9.2.1 並給出:

#include <stdint.h>
#include <string.h>
#if 1
uint16_t test(uint16_t *p)
{
    uint16_t q = *p;
    return q - (q >> 15);
}

它生成的代碼從*p讀取,然后從*(int16_t*)p讀取,將后者向右移動 15,並將其添加到前者。 如果*p的值在兩次讀取之間發生變化,這可能會導致函數返回 0xFFFF,這應該是不可能的值。

不幸的是,許多設計編譯器以便他們總是避免以這種方式“拆分”讀取的人認為這種行為足夠自然和明顯,因此沒有特別的理由明確記錄他們從不做任何其他事情的事實。 同時,其他一些編譯器作者認為,因為標准允許編譯器在沒有理由的情況下拆分讀取(拆分上述代碼中的讀取使其比僅讀取一次值時更大和更慢)任何代碼將依靠編譯器避免這種“優化”被“破壞”。

暫無
暫無

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

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