關於atomic到底安不安全

2021-10-07 16:42:42 字數 2767 閱讀 1617

atomic 實際上相當於乙個引用計數器,這個大家很熟悉,如果被標記了atomic,那麼被標記了的記憶體本身就有了乙個引用計數器,第乙個占用這塊記憶體的執行緒,會給這個計數器+1,在這個執行緒操作這塊記憶體期間,其他執行緒在訪問這個記憶體的時候,如果發現「引用計數器」不為0,則阻塞,實際上阻塞並不等於休眠,他是基於cpu輪詢片;休眠除非被叫醒,否則無法繼續執行,阻塞則不同,每個cpu 輪詢片到這個執行緒的時候都會嘗試繼續往下執行

首先我們從最基本的資料型別說起,char int long dobule 比如是這四種,如果在64位系統下,他們分別佔1、4、8、8 個位元組。想象一下,1個位元組也是記憶體,8個位元組也是記憶體,只要是記憶體,就有可能產生所謂的資源競爭,也就是多執行緒併發的問題。據我所知,char int 是絕對執行緒安全的,也就是說 系統對 char int 型別資料的操作,要麼不操作,要麼絕對會把這些位元組全部操作完,不會併發的問題。

再來說 long double ,對乙個long型資料的讀寫,作業系統有可能分兩部分進行,一部分是高位,一部分是低位,所以兩個執行緒同時操作long資料,有可能導致資料不同步,但是據我寫的demo測試,這種情況很難出現,因為乙個long型的資料最多也就8個位元組,對著8個位元組都是微妙甚至納秒級別,乙個cpu的輪詢片的週期,極大情況下都會比這個時間長,也就說,即使long 不是執行緒安全的,但是由於本身位元組非常少,讀寫速度極快,快到比cpu輪詢的時間片都塊的情況下,實際上即使不執行緒安全,兩個執行緒也不會同時讀寫這塊記憶體。

但凡事都有例外,demo 不能出現不代表他就是執行緒安全的,只能說絕大部分情況下,多執行緒操作乙個long 資料問題是不大的。 

繼續說double,double 型別的資料同樣佔8個位元組,按道理來說,他讀寫的速度應當和long 一樣快。實際上,也是這樣的。但是。乙個執行緒讀寫記憶體僅僅只是乙個方面,cpu 需要對資料進行計算,這個計算的中間結果一般都會放到暫存器或者cpu 快取記憶體,double 資料計算的複雜度遠非long型所能比,因此double 資料型別相比long 型資料更容易出現併發的問題。

說到這裡其實應該總結一下竅門了,什麼樣的資料會存在多執行緒的問題?什麼樣的資料不會呢?

可以想象一下,如果乙個資料佔的記憶體特別大,讀寫這塊資料需要的時間也就越長,如果這個時間長度遠遠超過執行緒排程的輪詢片,那麼就有極大可能出現併發問題。

你就記住,小於等於4個位元組的基本型別資料,比如char short int 等等等等,都是執行緒安全的,只要大於這個規定,都不是執行緒安全的。。。

ok 我們繼續討論一種特殊的資料型別,指標型別

我們知道,在64位的作業系統下,所有型別的指標,包括void * 都是占用8個位元組的。我們上面已經說了,超過4個位元組的基本型別資料都會有執行緒併發的問題。

那所有的指標型別都會有這個問題。

以oc 下的 nsarray * 為例子,如果乙個多執行緒操作這個資料,會有兩個層級的併發問題

1、指標本身

2、指標所指向的記憶體

ok 現在聯絡上atomic,如果用@property(atomic)nsarray *array 修飾之後,會有什麼影響?網上說的很多,不再贅述,我只想從記憶體的角度來解釋這個過程

首先第一點,你要記住,@property(atomic)nsarray *array 其實修飾的是這個指標,也就是這個8位元組記憶體,跟第二部分資料n位元組沒有任何關係,被atomic 修飾之後,你不可能隨意去多執行緒操作這個8位元組,但是對8位元組裡面所指向的n位元組沒有任何限制!這就是所有網路上所說的 atomic 不安全的真相!

自旋鎖已被證明不安全,同步鎖簡單,效能差,nslock 效能略好,dispatch_semphone 效能最好

現在我們有乙個8位元組的指標,假如我們做乙個初始化 nsarray *array = [[nsarray alloc] init] 這個操作。實際上這個操作有兩個意思

1:給8位元組賦值

2:開闢了一塊n位元組的記憶體區

我們只說這8位元組的位址複製,如果沒有atomic 修飾,並且假設現在有兩個執行緒正在操作這個指標,乙個就是上面的初始化執行緒,另乙個執行緒就是讀這個8位元組的指標

首先,假如8位元組內部存放的是0x1122334455667788 ok 8位元組需要寫入這個值,但與此同時,很不巧,另乙個讀執行緒現在要讀這個8位元組裡面的值

假如 這個8位元組只寫了一半的時候 另乙個執行緒來讀,那它讀到的可能是 0x1122334400000000 ok 實際上,等他讀完之後,寫執行緒仍然還未完成

這時候,[[nsarray alloc] init] 的頭位址正確的應該是0x1122334455667788 ,而讀執行緒讀到的是0x1122334400000000 這時候會出現什麼情況?

最好的情況,無非就是個野指標,因為誰也不知道這塊位址是否有效或者是否有什麼重要的資料,野指標會導致啥不多說了

最壞的情況,這個野位址指向的是重要的一段資料。。。後果可想而知。

所以 atomic 的意義就在於此,在0x1122334455667788 寫完之前,讀執行緒是無法讀取的,同樣的道理,在讀執行緒正在讀的過程中,寫執行緒是無法改變8位元組的 。atomic 能避免這8位元組的值因為多執行緒的原因被意外破壞,僅此而已。

假如現在有atomic 修飾,假如現在有兩個執行緒正在操作這個指標,根據上面的結論,他倆「先後」正確的獲取到了記憶體位址,也就說,他倆都先後、正確的找到了8位元組內容所指向的n位元組內容,雖然找到這n個位元組內容的順序有先後,但是不影響這兩個執行緒同時去操作這n個位元組的資料。

這樣問題又來了,兩個執行緒同時去操作n位元組內容,如果兩個執行緒都是讀執行緒,一般不會有問題,但是假如至少有乙個是寫執行緒,那問題又來了,還是乙個讀寫同步的問題,因此 atomic 雖然規範了 找到這n位元組內容的先後順序,但是它不能規範對著n個位元組內容的讀寫。這就是atomic 的侷限性。

如果是指標變數,需要加鎖,如果是基本變數,不用考慮,不需要加鎖 。

執行緒不安全

背景 執行緒不安全 sleep 模擬網路延遲 後多執行緒併發訪問同乙個資源 方法1 同步 塊 語法 synchronized 同步鎖 catch interruptedexception e 方法2 同步方法 使用synchronizd修飾的方法,就叫同步方法,保證a執行緒執行該方法的時候,其他執行...

執行緒安全與不安全

list介面下面有兩個實現 乙個是arraylist 另外乙個是vector 從原始碼的角度來看 vector的方法前加了,synchronized 關鍵字,也就是同步的意思,sun公司希望vector是執行緒安全的,而希望arraylist是高效的,缺點就是另外的優點。在 items size 的...

執行緒安全 不安全

hashmap 執行緒不安全 可以用concurrenthashmap,arraylist也有concurrent hashtable 執行緒安全,給每個方法都加上了synchronnized關鍵字。和concurrenthashmap的區別是加鎖粒度不同,不是很懂。stringbuilder 執行...