[英]Making data reads/writes atomic in C11 GCC using <stdatomic.h>?
對於 C11 原子,您甚至不必使用函數。 如果您的實現(= 編譯器)支持原子,您只需在變量聲明中添加一個原子說明符,然后對它的所有操作都是原子的:
_Atomic(int) toto = 65;
...
toto += 2; // is an atomic read-modify-write operation
...
if (toto == 67) // is an atomic read of toto
Atomics 有其價格(它們需要更多的計算資源),但只要您幾乎不使用它們,它們就是同步線程的完美工具。
如果我目前在一個線程中有一個 int 賦值:messageBox[i] = 2,我如何使這個賦值原子化? 同樣用於閱讀測試,例如 if (messageBox[i] == 2)。
你幾乎不需要做任何事情。 在幾乎所有情況下,您的線程共享(或與之通信)的數據都受到保護,不會通過互斥鎖、信號量等事物進行並發訪問。 基本操作的實現保證了內存的同步。
這些原子的原因是幫助您在代碼中構建更安全的競爭條件。 它們有許多危險; 包含:
ai += 7;
如果適當地定義了ai,將使用原子協議。 試圖破譯競爭條件並不能通過模糊實現來幫助。
他們還有一個高度依賴機器的部分。 例如,上面的行在某些平台上可能會失敗[1],但是這種失敗是如何反饋給程序的呢? 不是[2]。
只有一種操作可以選擇處理失敗; atomic_compare_exchange_(弱|強)。 Weak 只嘗試一次,讓程序選擇如何以及是否重試。 強重試無休止。 僅僅嘗試一次是不夠的——由於中斷可能會發生虛假故障——但對非虛假故障進行無休止的重試也不好。
可以說,對於健壯的程序或廣泛適用的庫,您應該使用的唯一位是 atomic_compare_exchange_weak()。
[1] 加載鏈接、條件存儲 (ll-sc) 是在異步總線架構上進行原子事務的常用方法。 加載鏈接在緩存行上設置一個小標志,如果任何其他總線代理嘗試修改該緩存行,該標志將被清除。 如果在緩存中設置了 little 標志,Store-conditional 存儲一個值,並清除該標志; 如果標志被清除,Store-conditional 會發出錯誤信號,因此可以嘗試適當的重試操作。 從這兩個操作中,您可以在完全異步的總線架構上構建您喜歡的任何原子操作。
ll-sc 可能對位置的緩存屬性有微妙的依賴性。 允許的緩存屬性取決於平台,因為可以在 ll 和 sc 之間執行哪些操作。
如果您對緩存不佳的訪問進行 ll-sc 操作,然后盲目重試,您的程序將鎖定。 這不僅僅是猜測; 我不得不在基於 ARMv7 的“安全”系統上調試其中之一。
[2]:
#include <stdatomic.h>
int f(atomic_int *x) {
return (*x)++;
}
f:
dmb ish
.L2:
ldrex r3, [r0]
adds r2, r3, #1
strex r1, r2, [r0]
cmp r1, #0
bne .L2 /* note the retry loop */
dmb ish
mov r0, r3
bx lr
假設多線程應用程序中的數據讀/寫在操作系統/硬件級別是原子的是不安全的,並且可能導致數據損壞
實際上,像int
這樣的類型的非復合操作在所有合理的架構上都是原子的。 你讀到的只是一個騙局。
(增量是一個復合操作:它有一個讀、一個計算和一個寫組件。每個組件都是原子的,但整個復合操作不是。)
但硬件級別的原子性不是問題。 您使用的高級語言根本不支持對常規類型的這種操作。 您需要使用原子類型甚至有權以原子性問題相關的方式操作對象:當您可能修改另一個線程中正在使用的對象時。
(或 volatile 類型。但不要使用 volatile。使用原子。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.