簡體   English   中英

在C11 / C ++ 11中,可以在同一個內存中混合原子/非原子操作嗎?

[英]In C11/C++11, possible to mix atomic/non-atomic ops on the same memory?

是否可以在同一個內存位置執行原子操作和非原子操作?

我問不是因為我真的想這樣做,而是因為我試圖理解C11 / C ++ 11內存模型。 他們定義了一個“數據競爭”,如下所示:

程序的執行包含數據競爭,如果它在不同的線程中包含兩個沖突的動作,其中至少有一個不是原子的,並且都不會在另一個之前發生。 任何此類數據爭用都會導致未定義的行為。 - C11§5.1.2.4P25,C ++ 11§1.10 p21基因

它的“至少有一個不是原子的”是困擾我的部分。 如果不可能混合原子和非原子操作,它只會說“在一個非原子的物體上”。

我看不到對原子變量執行非原子操作的任何直接方式。 C ++中的std::atomic<T>沒有定義任何非原子語義的操作。 在C中,原子變量的所有直接讀/寫似乎都被轉換為原子操作。

我想memcpy()和其他直接內存操作可能是對原子變量執行非原子讀/寫的方法嗎? 即。 memcpy(&atomicvar, othermem, sizeof(atomicvar)) 但這是否定義為行為? 在C ++中, std::atomic是不可復制的,所以在C或C ++中將它定義為memcpy()嗎?

原子變量的初始化(無論是通過構造函數還是atomic_init() )被定義為不是原子的。 但這是一次性操作:您不允許第二次初始化原子變量。 放置新的或顯式的析構函數調用也可能不是原子的。 但是在所有這些情況下,似乎不會定義行為,因為有一個可能在未初始化值上運行的並發原子操作。

對非原子變量執行原子操作似乎完全不可能:C和C ++都沒有定義任何可以對非原子變量進行操作的原子函數。

那么這里的故事是什么? 是真的關於memcpy() ,還是初始化/破壞,還是其他什么?

我認為你正在忽視另一個案例,相反的順序。 考慮一個初始化的int其存儲被重用於創建std::atomic_int 所有原子操作都在ctor完成后發生,因此在初始化內存上發生。 但是,對於現在被覆蓋的int任何並發的非原子訪問也必須被禁止。

(我在這里假設存儲壽命足夠,不起作用)

我不完全確定,因為我認為第二次訪問int無論如何都是無效的,因為訪問表達式int的類型與當時對象的類型( std::atomic<int> )不匹配。 但是,“對象的在時刻類型”假定它不在多線程環境中保持單個線性時間進展。 C ++ 11在總體上是解決了作出有關“ 全局狀態”未定義行為本身這樣的假設,並從問題的規定似乎適合在該框架內。

所以也許改寫:如果一個內存位置包含一個原子對象以及一個非原子對象,並且如果在創建另一個(較新的)對象之前對最早創建的(較舊的)對象的破壞沒有排序,那么對舊對象的訪問與對較新對象的訪問沖突,除非前者是在后者之前安排的。

免責聲明:我不是並行大師。

是否有可能在同一個內存中混合原子/非原子操作,如果是這樣,怎么樣?

你可以在代碼中編寫並編譯,但它可能會產生未定義的行為。

在談論原子論時,重要的是要了解他們解決了哪些問題。

您可能知道,我們在短時間內稱之為“內存”的是一組能夠保存內存的實體。
首先我們有RAM,然后是緩存行,然后是寄存器。

在單核處理器上,我們沒有任何同步問題。 在多核處理器上我們擁有所有這些。 每個核心都有自己的寄存器和緩存行。

這幾乎沒有問題。

其中第一個是內存重新排序 - CPU可以決定運行時來搜索一些讀/寫指令以使代碼運行得更快。 這可能會產生一些奇怪的結果,這些結果在帶來這組指令的高級代碼中完全不可見。
這個phenomanon最典型的例子是“兩個線程 - 兩個整數”的例子:

int i=0;
int j=0;
thread a -> i=1, then print j
thread b -> j=1 then print i;

從邏輯上講,結果“00”不可能。 或者首先結束,結果可以是“01”,或者b先結束,結果可以是“10”。 如果它們都在同一時間結束,則結果可能是“11”。 然而,如果你構建了一個小程序來模仿這個位置並在一個循環中運行它,非常quicly你會看到結果“00”

另一個問題是記憶隱形。 就像我之前提到的,變量的值可以緩存在其中一個緩存行中,或者存儲在其中一個注冊的緩存行中。 當CPU更新變量值時 - 它可能會延遲將新值寫回RAM。 它可能會保留緩存/注冊表中的值,因為它被告知(通過編譯器優化)該值將很快再次更新,因此為了使程序更快 - 再次更新值,然后再將其寫回內存。 如果其他CPU(以及線程或進程)依賴於新值,則可能導致未定義的行為。

例如,看看這個偽代碼:

bool b = true;
while (b) -> print 'a'
new thread -> sleep 4 seconds -> b=false;

字符'a'可以無限打印,因為b可能被緩存而且永遠不會被更新。

在處理訴訟時,還有很多問題。

atomics通過(簡而言之)告訴編譯器/ CPU如何正確地從RAM讀取數據和從RAM讀取數據而不做不想要的scrling(讀取內存命令 )來解決這些問題。 內存順序可能會強制cpu將其值寫回RAM,或者從RAM中讀取值,即使它們被緩存。

因此,雖然您可以將非原子動作與原子動作混合,但您只能完成部分工作。

例如,讓我們回到第二個例子:

atomic bool b = true;
while (reload b) print 'a'
new thread - > b = (non atomicly) false. 

因此,雖然一個線程一次又一次地從RAM重新讀取b的值,但另一個線程可能不會將false寫回RAM。

因此,雖然您可以在代碼中混合使用這些類型的操作,但它會產生不明確的行為。

暫無
暫無

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

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