同步
????????應(yīng)用程序中存在多個線程會導(dǎo)致潛在的問題睁枕,這些問題可能會導(dǎo)致從多個執(zhí)行線程安全訪問資源洽腺。修改相同資源的兩個線程可能會以非預(yù)期的方式相互干擾。例如噩翠,一個線程可能會覆蓋另一個線程的更改氛魁,或者將應(yīng)用程序置于未知的且無效的狀態(tài)。如果足夠幸運的話隆箩,損壞的資源可能會導(dǎo)致明顯的性能問題或者崩潰,這些問題相對容易追蹤和修復(fù)羔杨。然而捌臊,如果不幸運,就可能會導(dǎo)致微妙的錯誤兜材,直到很久以后才會出現(xiàn)理澎,或者錯誤可能需要對下層的代碼進行重大的檢測修復(fù)。
? ? ? ? 說到線程安全性曙寡,一個好的設(shè)計就是最好的保護糠爬。避免共享資源并盡量減少線程之間的交互使得線程不太可能相互干擾。然而举庶,完全無干擾的設(shè)計并不總是可行的执隧。在線程必須交互的情況下,使用同步工具來確保當(dāng)他們交付時户侥,他們可以安全地執(zhí)行镀琉。
????????OX X和iOS提供了許多同步工具供我們使用,包括提供互斥訪問的工具蕊唐,以及在應(yīng)用程序中正確排序的工具屋摔。下面各節(jié)將介紹這些工具以及如何在代碼中使用他們來影響對程序資源的安全訪問。
一刃泌、同步工具
????????為防止不同線程意外更改數(shù)據(jù)凡壤,可以設(shè)計應(yīng)用以避免同步問題,也可以使用同步工具耙替。盡管完全避免同步問題是可取的亚侠,但并非總是可行的。下面各節(jié)介紹可供使用的同步工具基本類別俗扇。
1硝烂、原子操作
????????原子操作是一種簡單的同步形式,適用于簡單的數(shù)據(jù)類型铜幽。原子操作的優(yōu)點是他們不會阻塞競爭線程滞谢。對于簡單的操作,例如增加一個計數(shù)器變量除抛,這可能會導(dǎo)致比鎖定更好的性能狮杨。
? ? ? ? OS X和iOS包含許多操作,以便對32位和64位值執(zhí)行基本的數(shù)學(xué)和邏輯運算到忽。這些操作包括比較和交換橄教,測試和設(shè)置以及測試和清除操作的原子版本清寇。有關(guān)受支持的原子操作列表,請參閱/usr/include/libkern/OSAtomic.h頭文件或查看原子手冊頁护蝶。
?2华烟、內(nèi)存屏障(memory barrier)和易變變量(Volatile variables)
????????為了達到最佳性能,編譯器通常會對匯編級指令進行重新排序持灰,以盡可能保持處理器的指令流水線盔夜。作為這種優(yōu)化的一部分,編譯器可能會重新排序訪問主內(nèi)存的指令堤魁,因為它認(rèn)為這樣做不會產(chǎn)生不正確的數(shù)據(jù)喂链。不幸的是,編譯器并不總是能夠檢測到所有依賴于內(nèi)存的操作姨涡。如果看似單獨的變量實際上相互影響衩藤,編譯器優(yōu)化可能會以錯誤的順序更新這些變量,從而產(chǎn)生潛在的錯誤結(jié)果涛漂。
? ? ? ? 內(nèi)存屏障(memory barrier)是一種非阻塞同步工具赏表,用于確保內(nèi)存操作以正確的順序執(zhí)行。內(nèi)存屏障就像柵欄一樣匈仗,強制處理器完成位于柵欄前的任何加載和存儲操作瓢剿。內(nèi)存屏障通常用于確保一個線程(但對另一個線程可見)的內(nèi)存操作始終按預(yù)期的順序進行。在這種情況下缺乏內(nèi)存屏障可能會讓其他線程看到看似不可能的結(jié)果悠轩。請參閱維基百科memory barriers间狂。要使用內(nèi)存屏障,只需要在代碼中適當(dāng)位置調(diào)用OSMemoryBarrier函數(shù)即可火架。
? ? ? ? 易變變量(Volatile variables)將另一種類型的記憶約束應(yīng)用于單個變量鉴象。編譯器通常通過將變量的值加載到寄存器中來優(yōu)化代碼。對于局部變量何鸡,這通常不是問題纺弊。如果變量從另一個線程可見,但是這樣的優(yōu)化可能會阻止其他線程注意到它的任何更改骡男。將volatile關(guān)鍵字應(yīng)用于變量會強制編譯器在每次使用時從內(nèi)存加載該變量淆游。如果無法隨時通過編譯器檢測到外部源值得更改,則可以將變量聲明為volatile隔盛。
? ? ? ? 因為內(nèi)存屏障和volatile變量都會減少編譯器可以執(zhí)行的優(yōu)化次數(shù)犹菱,所以應(yīng)該謹(jǐn)慎使用它們,并且只在需要時才能保證正確性吮炕。有關(guān)使用內(nèi)存屏障的信息腊脱,請參閱OSMemoryBarrier手冊。
3龙亲、鎖
????????鎖是最常用的同步工具之一陕凹。我們可以使用鎖來保護代碼的關(guān)鍵部分震鹉,這一段代碼,一次只允許一個線程訪問捆姜。例如,關(guān)鍵部分可能會操作特定的數(shù)據(jù)結(jié)構(gòu)或使用一次最多支持一個客戶端的資源迎膜。通過在這里放置一個鎖泥技,可以排除其他線程進行可能影響代碼正確性的更改。
????????下面列出了我們常使用的一些鎖磕仅。OS X和iOS為大多數(shù)這些鎖提供了實現(xiàn)珊豹,但不是全部。對于不支持的鎖定類型榕订,說明部分解釋了為什么這些鎖不能直接在平臺上實現(xiàn)的原因店茶。
3.1、鎖的類型
3.1.1劫恒、互斥鎖
????????互斥鎖作為資源周圍的保護屏障贩幻。互斥鎖是一種信號量两嘴,一次只允許訪問一個線程丛楚。如果一個互斥體正在使用,而另一個線程試圖獲取它憔辫,則該線程將阻塞趣些,直到互斥體被其原始持有者釋放。如果多個線程競爭相同的互斥量贰您,則一次只允許一個線程訪問它坏平。
3.1.2、遞歸鎖
????????遞歸鎖是互斥鎖的變體锦亦。遞歸鎖允許單個線程在釋放之前多次獲取鎖定舶替。其他線程會一直處于阻塞狀態(tài),直到鎖的所有者釋放該鎖的次數(shù)與獲取它的次數(shù)相同孽亲。遞歸鎖主要在遞歸迭代期間使用坎穿,但也可能在多個方法需要分別獲取鎖的情況下使用。
3.1.3返劲、讀寫鎖
? ? ? ? 讀寫鎖也被稱為共享排他鎖玲昧。這種類型的鎖通常用于較大規(guī)模的操作,如果經(jīng)常讀取受保護的數(shù)據(jù)結(jié)構(gòu)并偶爾進行修改篮绿,則可顯著地提高性能孵延。在正常操作期間,多個讀取器可以同時訪問數(shù)據(jù)結(jié)構(gòu)亲配。然而尘应,當(dāng)一個線程想要寫入該結(jié)構(gòu)時惶凝,它會阻塞,直到所有的讀取器釋放該鎖犬钢,此時它獲得鎖并可以更新該結(jié)構(gòu)苍鲜。寫入線程正在等待鎖定時,新的讀取器線程將阻塞玷犹,直到寫入線程完成混滔。系統(tǒng)僅支持使用POSIX線程的讀寫鎖定。有關(guān)如何使用這些鎖的更多信息歹颓,請參見pthread手冊頁坯屿。
3.1.4、分布式鎖
????????分布式鎖在進程級別提供互斥訪問巍扛。與真正的互斥鎖不同领跛,分布式鎖不會阻塞進程或阻止進程運行。它只是報告鎖何時忙撤奸,并讓流程決定如何繼續(xù)吠昭。
3.1.5、自旋鎖
????????自旋鎖反復(fù)輪詢其鎖定條件胧瓜,直到該條件成立怎诫。自旋鎖最常用于預(yù)計等待鎖定時間較短的多處理器系統(tǒng)。在這些情況下贷痪,輪詢通常比攔截線程更有效幻妓,后者涉及上下文切換和線程數(shù)據(jù)結(jié)構(gòu)的更新。由于輪詢性質(zhì)劫拢,系統(tǒng)不提供自旋鎖的任何實現(xiàn)肉津,但我們可以在特定情況下輕松實現(xiàn)它們。有關(guān)在內(nèi)核中實現(xiàn)自旋鎖的信息舱沧,請參閱內(nèi)核編程指南妹沙。
3.1.6、雙重檢查鎖
????????雙重檢查鎖試圖通過鎖定之前測試鎖定標(biāo)準(zhǔn)來降低獲取鎖的開銷熟吏。由于雙重檢查的鎖可能不安全距糖,系統(tǒng)不提供對他們的明確支持,因此不鼓勵使用它們牵寺。
注意:大多數(shù)類型的鎖還包含內(nèi)存屏障(memory barrier),以確保在進入臨界區(qū)之前完成任何前面加載和存儲指令悍引。有關(guān)如何使用鎖定的信息,請參閱使用鎖定帽氓。
3.2趣斤、條件
????????條件是另一種類型的信號量,它允許線程在特定條件為彼此發(fā)送信號黎休。條件通常用于指示資源的可用性或確保任務(wù)按特定順序執(zhí)行浓领。當(dāng)一個線程測試一個條件時玉凯,它會阻塞,除非該條件已經(jīng)成立联贩。直到其他線程明確更改并發(fā)出信號漫仆,它才會被阻塞。條件和互斥鎖之間的區(qū)別在于可能允許多個線程同時訪問條件泪幌。這種情況更多的是一個看門人歹啼,它根據(jù)一些特定標(biāo)準(zhǔn)讓不同的線程通過門。
????????我們可能會使用一個條件的一種方法管理未決的事件池座菠。當(dāng)隊列中有事件時。事件隊列將使用條件變量來發(fā)送信號通知等待線程藤树。如果有一個事件到達浴滴,隊列會適當(dāng)?shù)匕l(fā)出信號。如果一個線程已經(jīng)等待岁钓,它會被喚醒升略,然后它將把事件從隊列中拉出來并處理它。如果兩個事件大致同時進入隊列屡限,則隊列將發(fā)送兩次通知狀態(tài)以喚醒兩個線程品嚣。
該系統(tǒng)為幾種不同技術(shù)的條件提供支持。然而钧大,正確的條件實現(xiàn)需要仔細的編碼翰撑,所以我們應(yīng)該在使用條件之前查看自己代碼中的例子。
3.3啊央、執(zhí)行選擇器
????????Cocoa應(yīng)用程序有一種方便的方式將消息以同步方式傳遞給單個線程眶诈。NSObject類聲明了在應(yīng)用程序的一個活動線程上執(zhí)行選擇器的方法。這些方法允許線程異步傳遞消息瓜饥,并保證他們將由目標(biāo)線程同步執(zhí)行逝撬。例如,可以使用執(zhí)行選擇器消息將分布式計算的結(jié)果傳遞到應(yīng)用程序的主線程或指定的協(xié)調(diào)器線程乓土。執(zhí)行選擇器的每個請求都在目標(biāo)線程的運行循環(huán)中排隊宪潮,然后按接收到的順序處理請求。
有關(guān)執(zhí)行選擇器的摘要以及有關(guān)如何使用它們的更多信息趣苏,請參閱Cocoa執(zhí)行選擇器源狡相。
二、同步的代價和性能
? ? ? ? 同步有助于確保代碼的正確性食磕,但這樣做會犧牲性能谣光。同步工具的使用會引入延遲,即使在無爭議的情況下也是如此芬为。鎖和原子操作通常涉及使用內(nèi)存屏障和內(nèi)核級同步來確保代碼得到適當(dāng)?shù)谋Wo萄金。而且如果有鎖爭用蟀悦,我們的線程可能會阻止并經(jīng)歷更大的延遲。
? ? ? ? 下面列出了在無爭議的情況下與互斥體和原子操作相關(guān)的一些近似成本氧敢。這些測量值代表了幾千個樣本的平均時間日戈。與線程創(chuàng)建時間一樣,互斥量采集時間(即使在無爭議的情況下)也會因處理器負(fù)載孙乖,計算機速度以及可用系統(tǒng)和程序存儲器的數(shù)量而有很大差異浙炼。
1.互斥量和原子操作的代價對比
????????<1>.互斥量采集時間,大約0.2微妙唯袄,這是無爭議的情況下的鎖定獲取時間弯屈。如果鎖由另一個線程保存,則獲取時間可能會更長恋拷。這些數(shù)據(jù)是通過分析采用基于Intel的iMac(采用2 GHz Core Duo處理器和1 GB運行OS X v10.5的RAM)獲取互斥量過程中生成的平均值和中值來確定的资厉。
????????<2>.原子比較和交換,大約0.05微妙蔬顾,這是無爭議的情況下的比較和交換時間宴偿。這些數(shù)字是通過分析操作的平均值和中值確定的,并且是基于Intel的iMac上使用2 Ghz Core Duo處理器和1 GB運行OS X v10.5的RAM生成的诀豁。
????????在設(shè)計并發(fā)任務(wù)時窄刘,正確性始終是最重要的因因素,但我們也應(yīng)該考慮性能因素舷胜。在多線程下正確執(zhí)行的代碼娩践,比在單個線程上運行慢的代碼幾乎沒有改進。
? ? ? ? 如果我們正在改進現(xiàn)有的單線程應(yīng)用程序烹骨,則應(yīng)始終對關(guān)鍵任務(wù)的性能進行一系列基準(zhǔn)測量欺矫。在添加額外的線程后,應(yīng)該對這些相同的任務(wù)進行新的測量展氓,并將多線程案例的性能和單線程案例進行比較穆趴。如果在調(diào)整代碼之后,線程并不會提高性能遇汞,則可能需要重新考慮關(guān)鍵任務(wù)的實現(xiàn)內(nèi)容未妹。
? ? ? ? 有關(guān)性能和搜集指標(biāo)的工具的信息,請參閱性能概述空入。有關(guān)鎖和原子操作成本的具體信息络它,請參閱線程成本。
三歪赢、線程安全和信號
????????當(dāng)涉及到線程應(yīng)用時化戳,沒有什么比處理信號更令人擔(dān)心或者混亂。信號是一種低級別的BSD機制,可用于向進程傳遞信息或以某種方式操作它点楼。一些程序使用信號來檢測某些事件扫尖,例如子進程的死亡。該系統(tǒng)使用信號來終止失控進程并傳達其他類型的信息掠廓。
? ? ? ? 信號問題是應(yīng)用程序使用多線程時的一種行為换怖。在單個線程應(yīng)用程序中,所有的信號處理程序都在主線程上運行蟀瞧。在多線程應(yīng)用程序中沉颂,與特定硬件錯誤無關(guān)的信號將傳送到當(dāng)時正在運行的線程。如果多個線程同時運行悦污,則信號被傳送到系統(tǒng)隨機挑選的任何一個線程铸屉。也就是信號可以傳送到應(yīng)用程序的任何線程。
? ? ? ? 在應(yīng)用程序中實現(xiàn)信號處理程序的第一條規(guī)則是避免假設(shè)哪個線程正在處理信號切端。如果某個特定的線程想要處理給定的信號彻坛,則需要在信號到達時通過某種方式通知該線程。所以不能假設(shè)獲取信號后信號的處理線程一定是目標(biāo)處理線程帆赢。
四、線程安全設(shè)計的技巧
????????同步工具是使代碼線程安全的有用方法线梗,但它們不是萬能的椰于。與非線程性能相比,使用太多的鎖和其他類型的同步操作實際上會降低應(yīng)用程序的線程性能仪搔。找到安全和性能之間的正確平衡是一門需要經(jīng)驗的藝術(shù)瘾婿。下面提供的技巧可以幫助我們?yōu)閼?yīng)用程序選擇適當(dāng)?shù)耐郊墑e。
1烤咧、避免完全同步 ? ? ? ?
????????對于任何新的項目偏陪,甚至是現(xiàn)有的項目,設(shè)計代碼和數(shù)據(jù)結(jié)構(gòu)以避免同步的需求是最好的解決方案煮嫌。盡管鎖和其他同步工具很有用笛谦,但他們的確會影響應(yīng)用程序的性能。如果整體設(shè)計造成特定資源之間的頻繁爭用昌阿,線程可能會等待時間更長饥脑。
? ? ? ? 實現(xiàn)并發(fā)的最好方法是減少并發(fā)任務(wù)之間的交互和相互依賴。如果每個任務(wù)都在自己的專用數(shù)據(jù)集上運行懦冰,則不需要使用鎖保護該數(shù)據(jù)灶轰。即使在兩個任務(wù)共享一個通用數(shù)據(jù)集的情況下,也可以查看分區(qū)的方式或為每個任務(wù)提供自己的副本刷钢。當(dāng)然笋颤,復(fù)制數(shù)據(jù)集也會帶來成本,因此必須在作出決定之前權(quán)衡兩者的成本内地。
2伴澄、了解同步的限制
同步工具只有在應(yīng)用程序中的所有線程一直使用它們時才有效赋除。如果創(chuàng)建一個互斥體來限制對特定資源的訪問,則所有線程都必須在嘗試操作資源之前獲取相同的互斥體秉版。如果不這樣做會破壞互斥體提供的保護贤重。
3、注意代碼正確的威脅
在使用鎖和內(nèi)存屏障時清焕,應(yīng)該仔細考慮它們在代碼中的位置并蝗。即使看起來很好的鎖也能讓你陷入虛假的安全感。下面示例顯示了看起來沒有問題的代碼之中的缺陷秸妥×揖基本前提是有一個包含一組不可變對象的可變數(shù)組。假設(shè)調(diào)用數(shù)組中第一個對象的方法墙懂〈倚澹可以使用如下代碼:
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[arrayLock unlock];
[anObject doSomething];
????????因為數(shù)組是可變的,所以數(shù)組周圍的鎖可以防止其他線程修改數(shù)組突雪,直到獲取所需的對象起惕。由于檢索的對象本身是不可變的,因此在調(diào)用doSomething方法時不需要鎖定咏删。
不過惹想,前面的例子存在問題。如果釋放鎖并且有一個線程進來并在有機會執(zhí)行doSomething方法之前刪除數(shù)組中的所有對象督函,會發(fā)生什么嘀粱?在沒有垃圾收集的應(yīng)用程序中,代碼持有的對象可能會被釋放辰狡,從而導(dǎo)致anObject指向無效的內(nèi)存地址锋叨。要解決這個問題,我們可能會重新安排現(xiàn)有的代碼宛篇,并在調(diào)用doSomething后釋放鎖娃磺,如下所示:
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject doSomething];
[arrayLock unlock];
? ? ? ? 通過在鎖內(nèi)調(diào)用doSomething,可以確保在調(diào)用方法時對象仍然有效叫倍。不幸的是豌鸡,如果doSomething方法需要很長時間才能執(zhí)行,這可能會導(dǎo)致代碼長時間保持鎖定狀態(tài)段标,這可能會導(dǎo)致性能瓶頸涯冠。
? ? ? ? 代碼問題并不在于關(guān)鍵區(qū)域定義不明確,而是實際問題沒有得到理解逼庞。真正的問題是內(nèi)存管理問題只能由其他線程觸發(fā)蛇更。因為它可以被另一個線程釋放,所以更好的解決方案是在釋放鎖之前保留anObject。這個解決方案解決了被釋放對象的實際問題派任,并且不會引入潛在的性能損失砸逊。
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject retain];
[arrayLock unlock];
[anObject doSomething];
[anObject release]
????????雖然上面的例子非常簡單,但他們確實說明了一個非常重要的問題掌逛。說到正確性师逸,我們必須考慮明顯問題之外的其他問題。內(nèi)存管理和設(shè)計的其他方面也可能會受到多線程的影響豆混,所以必須事先考慮這些問題篓像。另外,我們應(yīng)該總是假設(shè)編譯器在安全方面是一個白癡皿伺。只有時刻保持這種意識和警惕性才可以最大的避免潛在的問題并確保代碼執(zhí)行的安全员辩。
? ? ? ? 有關(guān)如何使程序線程安全的其他示例,請參閱線程安全摘要鸵鸥。
4奠滑、注意死鎖和活鎖
????????當(dāng)我們試圖在一個線程使用多個鎖時,就有可能發(fā)生死鎖妒穴。當(dāng)兩個不同的線程持有一個線程需要的鎖并嘗試獲取另一個線程持有的鎖時宋税,會發(fā)生死鎖。結(jié)果是每個線程永久阻塞讼油,因為它永遠無法獲取其他鎖杰赛。
? ? ? ? 活鎖類似于其他死鎖,并且在兩個線程競爭同一組資源時發(fā)生汁讼。在活鎖情況下淆攻,一個線程在試圖獲得第二個鎖時放棄它的第一個鎖阔墩。一旦它獲得第二個鎖嘿架,他就會返回并嘗試再次獲取第一個鎖。它鎖定了啸箫,因為它花費所有時間釋放一個鎖并試圖獲取另一個鎖耸彪,而不是做任何真正的工作。
? ? ? ? 避免死鎖和活鎖情況的最好方法是一次只取一個鎖忘苛。如果一次獲取一個以上的鎖蝉娜,應(yīng)該確保其他線程不會嘗試做類似的事情。
?5扎唾、正確使用易變變量
????????如果已經(jīng)使用互斥體來保護一段代碼召川,就不要自動假設(shè)需要使用volatile關(guān)鍵字來保護該部分中的重要變量⌒赜觯互斥體包含一個內(nèi)存屏障荧呐,以確保加載和存儲操作的正確順序。將volatile關(guān)鍵字添加到臨界區(qū)域內(nèi)的變量會強制每次訪問時從內(nèi)存加載該值。兩種技術(shù)的組合在特定情況下可能是必要的倍阐,但也會導(dǎo)致顯著的性能損失概疆。如果只有互斥量足以保護變量,則省略volatile關(guān)鍵字峰搪。
? ? ? ? 為避免使用互斥體岔冀,不要使用volatile變量也很重要。一般來說概耻,互斥鎖和其他同步機制是保護數(shù)據(jù)結(jié)構(gòu)完整性的一種更好的方式使套,而不是易變變量。volatile關(guān)鍵字只確保變量從內(nèi)存中加載而不是存儲在寄存器中咐蚯。它不能確保代碼正確訪問該變量童漩。
五、使用原子操作
? ? ? ? 非阻塞同步是執(zhí)行某些類型的操作并避免鎖的開銷的一種方式春锋。盡管鎖是同步兩個線程的有效方法矫膨,但即使在無爭議的情況下,獲取鎖也是一項相對消耗性能的操作期奔。相比之下侧馅,許多原子操作需要一小部分時間來完成,并且可以像鎖一樣有效呐萌。
? ? ? ? 原子操作允許對32位和64位值執(zhí)行簡單的數(shù)學(xué)和邏輯運算馁痴。這些操作依賴于特殊的硬件指令,以確保在受影響的內(nèi)存再次之前完成給定的操作肺孤。在多線程情況下罗晕,應(yīng)始終使用包含內(nèi)存屏障的原子操作來確保內(nèi)存在線程之間正確同步。
? ? ? ? 下面列出了可用的原子數(shù)學(xué)和邏輯運算以及相應(yīng)的函數(shù)名稱赠堵。這些函數(shù)都在/usr/include/libkern/OSAtomic.h頭文件中聲明小渊,我們可以在其中找到完整的語法。這些函數(shù)的64位版本僅在64位進程中可用茫叭。
1酬屉、原子的數(shù)學(xué)和邏輯操作
<1>.添加(Add)
OSAtomicAdd32、?OSAtomicAdd32Barrier揍愁、 OSAtomicAdd64呐萨、 OSAtomicAdd64Barrier
將兩個整數(shù)值一起添加并將結(jié)果存儲在指定變量的其中之一。
<2>.增量(Increment)
OSAtomicIncrement32莽囤、?OSAtomicIncrement32Barrier谬擦、?OSAtomicIncrement64 、OSAtomicIncrement64Barrier
將指定的整數(shù)值增加1.
<3>.遞減(Decrement)
OSAtomicDecrement32朽缎、?OSAtomicDecrement32Barrier惨远、?OSAtomicDecrement64蔚舀、?OSAtomicDecrement64Barrier
將指定的整數(shù)值減1.
<4>.邏輯或(Logical OR)
OSAtomicOr32、?OSAtomicOr32Barrier
在指定的32位值和32位掩碼之間執(zhí)行邏輯或锨络。
<5>.邏輯與(Logical AND)
OSAtomicAnd32赌躺、?OSAtomicAnd32Barrier
在指定的32位值和32位掩碼之間執(zhí)行邏輯與。
<6>.邏輯異或(Logical XOR)
OSAtomicXor32羡儿、OSAtomicXor32Barrier
在指定的32位值和32位掩碼之間執(zhí)行邏輯異或礼患。
<7>.比較和交換
OSAtomicCompareAndSwap32、?OSAtomicCompareAndSwap32Barrier掠归、OSAtomicCompareAndSwap64缅叠、?OSAtomicCompareAndSwap64Barrier、?OSAtomicCompareAndSwapPtr虏冻、?OSAtomicCompareAndSwapPtrBarrier肤粱、?OSAtomicCompareAndSwapInt、?OSAtomicCompareAndSwapIntBarrier厨相、?OSAtomicCompareAndSwapLong领曼、?OSAtomicCompareAndSwapLongBarrier?
將變量與指定的舊值進行比較。 如果兩個值相等蛮穿,則該函數(shù)將指定的新值賦給變量; 否則庶骄,它什么都不做。 比較和賦值是作為一個原子操作完成的践磅,函數(shù)返回一個布爾值单刁,指示實際上是否發(fā)生了交換。
<8>.測試并設(shè)置
OSAtomicTestAndSet府适、?OSAtomicTestAndSetBarrier
測試指定變量中的一位羔飞,將該位設(shè)置為1,并將舊位的值作為布爾值返回檐春。根據(jù)字節(jié)((char *)地址+(n >> 3))的公式(0x80 >>(n&7))對位進行測試逻淌,其中n是位編號,地址是指向變量的指針喇聊。該公式將變量有效分解為8位大小的塊恍风,并將每個塊中的位反向排序蹦狂。例如誓篱,要測試一個32位整數(shù)的最低位(位0),實際上應(yīng)該為位編號指定7;類似地凯楔,為了測試最高位(位32)窜骄,應(yīng)該指定24位的位數(shù)。
<9>.測試和清除
OSAtomicTestAndClear摆屯、OSAtomicTestAndClearBarrier
測試指定變量中的一位邻遏,將該位設(shè)置為0糠亩,并將舊位的值作為布爾值返回。根據(jù)字節(jié)((char *)地址+(n >> 3))的公式(0x80 >>(n&7))對位進行測試准验,其中n是位編號赎线,地址是指向變量的指針。該公式將變量有效分解為8位大小的塊糊饱,并將每個塊中的位反向排序垂寥。例如,要測試一個32位整數(shù)的最低位(位0)另锋,實際上應(yīng)該為位編號指定7;類似地滞项,為了測試最高位(位32),應(yīng)該指定24位的位數(shù)夭坪。
????????大多數(shù)原子函數(shù)的行為應(yīng)該是相對直接的文判,并且與預(yù)期相同。 然而室梅,下面顯示了原子測試和設(shè)置以及比較和交換操作的行為戏仓,這些操作稍微復(fù)雜一些。 對OSAtomicTestAndSet函數(shù)的前三個調(diào)用演示了如何在整數(shù)值上使用位操作公式亡鼠,其結(jié)果可能與期望的不同柜去。 最后兩個調(diào)用顯示OSAtomicCompareAndSwap32函數(shù)的行為。 在所有情況下拆宛,當(dāng)沒有其他線程正在操作這些值時嗓奢,這些函數(shù)在無爭議的情況下被調(diào)用。 ? ?
????????執(zhí)行原子操作:
int32_t theValue = 0;
OSAtomicTestAndSet(0浑厚,&theValue);
// theValue現(xiàn)在是128股耽。
theValue = 0;
OSAtomicTestAndSet(7,&theValue);
// theValue現(xiàn)在是1钳幅。
theValue = 0;
OSAtomicTestAndSet(15物蝙,&theValue)
// theValue現(xiàn)在是256。
OSAtomicCompareAndSwap32(256,512敢艰,&theValue);
// theValue現(xiàn)在是512诬乞。
OSAtomicCompareAndSwap32(256,1024钠导,&theValue);
// theValue仍然是512震嫉。
????????有關(guān)原子操作的信息,請參閱原子手冊頁和/usr/include/libkern/OSAtomic.h頭文件牡属。
六票堵、使用鎖
? ? ? ? 鎖是線程編程的基本同步工具。鎖可以輕松保護代碼逮栅,以確保代碼的正確性悴势。OS X和iOS為所有應(yīng)用程序類型提供基本的互斥鎖窗宇,F(xiàn)oundation框架為特殊情況定義了互斥鎖的一些其他變體。下面將展示如何使用其中幾種鎖的使用特纤。
1军俊、使用POSIX互斥鎖
POSIX互斥鎖非常易于在任何應(yīng)用程序中使用。要創(chuàng)建互斥鎖捧存,可以聲明和解鎖互斥鎖蝇完,可以使用pthread_mutex_lock和pthread_mutex_unlock函數(shù)。下面顯示了初始化和是使用POSIX線程互斥鎖所需的基本代碼矗蕊。當(dāng)完成鎖定時短蜕,只需要調(diào)用pthread_mutex_destory釋放鎖定數(shù)據(jù)結(jié)構(gòu)即可。
? ? ? ? 互斥鎖使用
pthread_mutex_t mutex;
void MyInitFunction()
{
? ??pthread_mutex_init(&mutex, NULL);
}
void MyLockingFunction()
{
? ??pthread_mutex_lock(&mutex);
? ??// Do work.
? ??pthread_mutex_unlock(&mutex);
}
注意:以上代碼只是一個簡化的互斥鎖的示例傻咖,旨在顯示POSIX線程互斥鎖的基本用法朋魔。真實的代碼中應(yīng)該檢查這些函數(shù)返回的錯誤代碼,并對錯誤做出適當(dāng)?shù)奶幚怼?/p>
2卿操、使用NSLock類
????????一個NSLock對象為Cocoa應(yīng)用程序?qū)崿F(xiàn)了一個基本的互斥鎖警检。所有鎖的接口(包括NSLock)實際上都由NSLocking協(xié)議定義,該協(xié)議定義了鎖定和解鎖方法害淤∩鹊瘢可以向使用互斥鎖一樣使用這些方法獲取和釋放鎖。
????????除了標(biāo)準(zhǔn)的鎖定行為之外窥摄,NSLock類還添加了tryLock和lockBeforeDate:方法镶奉。tryLock方法嘗試獲取鎖,但不鎖定鎖是否不可用崭放;相反哨苛,該方法只返回NO。lockBeforeDate:方法嘗試獲取鎖币砂,但如果在指定的時間限制內(nèi)未獲取鎖建峭,則對該線程加鎖(并返回NO)。
下面顯示了如何使用NSLock對象來協(xié)調(diào)可視顯示的更新决摧,該顯示的數(shù)據(jù)由多個線程計算亿蒸。如果線程無法立即獲取鎖,則只需繼續(xù)其計算掌桩,直到它可以獲取鎖并更新边锁。
BOOL moreToDo = YES;
? ??NSLock *theLock = [[NSLock alloc] init];
? ??...
? ??while (moreToDo) {
? ??????/* Do another increment of calculation */
? ??????/* until there’s no more to do. */
? ? ? ??if ([theLock tryLock]) {
? ??????/* Update display used by all threads. */
? ??????[theLock unlock];
? ??}
}
3、使用@synchronized指令
????????@synchronized指令是在Objective-C代碼中快速創(chuàng)建互斥鎖的一種便捷方式拘鞋。@synchronized指令執(zhí)行任何其他互斥鎖都會執(zhí)行的操作 - 它可以防止不同線程同時獲取同一個鎖砚蓬。 但是矢门,在這種情況下盆色,不必直接創(chuàng)建互斥鎖或鎖定對象灰蛙。 相反,只需使用任何Objective-C對象作為鎖定標(biāo)記即可隔躲,如以下示例所示:
- (void)myMethod:(id)anObj
{
? ??@synchronized(anObj){
? ??????// Everything between the braces is protected by the @synchronized directive.
? ??}
}
????????傳遞給@synchronized指令的對象是用于區(qū)分受保護塊的唯一標(biāo)識符摩梧。如果在兩個不同的線程中執(zhí)行上述方法,則在每個線程上傳遞anObj參數(shù)的不同對象宣旱,每個線程都會鎖定并繼續(xù)處理而不被另一個線程阻塞仅父。但是,如果在兩種情況下都傳遞相同的對象浑吟,則其中一個線程將首先獲取鎖笙纤,另一個會阻塞,直到第一個線程完成臨界區(qū)组力。
????????作為預(yù)防措施省容,@synchronized塊隱式地將一個異常處理程序添加到受保護的代碼中。如果引發(fā)異常燎字,該處理程序會自動釋放互斥鎖腥椒。這意味著為了使用@synchronized指令,還必須在代碼中啟用Objective-C異常處理候衍。如果不想由隱式異常處理程序引起的額外開銷笼蛛,則應(yīng)考慮使用鎖類。
????????有關(guān)@synchronized指令的更多信息蛉鹿,請參閱Objective-C編程語言滨砍。
4、使用其他的Cocoa鎖
????????下面各節(jié)介紹如何使用其他幾種類型的Cocoa鎖的過程妖异。
4.1惨好、使用一個NSRecursiveLock(遞歸鎖)對象
? ??????NSRecursiveLock類定義了一個鎖,它可以被同一個線程多次獲取而不會導(dǎo)致線程死鎖随闺。 遞歸鎖會記錄它成功獲取的次數(shù)日川。 每次成功獲取鎖必須通過相應(yīng)的解鎖才能進行平衡。 只有當(dāng)所有的鎖定和解鎖調(diào)用是平衡的矩乐,鎖定才會被釋放龄句,以便其他線程可以獲得它。
? ? ? ? 也就是說散罕,這種類型的鎖常用于遞歸函數(shù)中分歇,以防止遞歸阻塞線程。 也可以類似地在非遞歸的情況下使用它來調(diào)用語義要求也鎖定的函數(shù)欧漱。 下面是一個簡單遞歸函數(shù)的例子职抡,它通過遞歸來獲取鎖。 如果您沒有為此代碼使用NSRecursiveLock對象误甚,則當(dāng)再次調(diào)用該函數(shù)時缚甩,該線程將死鎖谱净。
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value)
{
? ??[theLock lock];
? ??if (value != 0)
? ??{
? ??????--value;
? ??????MyRecursiveFunction(value);
? ??}
? ??[theLock unlock];
? ??}
MyRecursiveFunction(5);
注意:因為在所有鎖定調(diào)用與解鎖調(diào)用保持平衡之前,遞歸鎖定不會釋放擅威,因此應(yīng)該仔細衡量鎖定的使用性能以應(yīng)對潛在性能影響的決定壕探。 長時間保持鎖定會導(dǎo)致其他線程阻塞,直到遞歸完成郊丛。 如果可以重寫代碼以消除遞歸或消除使用遞歸鎖定的需要李请,則可能會獲得更好的性能。
4.2厉熟、使用一個NSConditionLock(條件鎖)對象
一個NSConditionLock對象定義了一個可以使用特定值鎖定和解鎖的互斥鎖导盅。不應(yīng)該將這種類型的鎖與條件混淆。行為與條件有些類似揍瑟,但實施方式差異很大认轨。
通常,當(dāng)線程需要按特定順序執(zhí)行任務(wù)時(例如月培,當(dāng)一個線程產(chǎn)生另一個線程產(chǎn)生的數(shù)據(jù)時)嘁字,可以使用NSConditionLock對象。在生產(chǎn)者正在執(zhí)行時杉畜,消費者使用特定于程序的條件獲取鎖纪蜒。 (條件本身只是您定義的整數(shù)值。)當(dāng)生產(chǎn)者完成時此叠,它解鎖鎖并將鎖條件設(shè)置為適當(dāng)?shù)恼麛?shù)值以喚醒消費者線程纯续,然后消費者線程繼續(xù)處理數(shù)據(jù)。
NSConditionLock對象響應(yīng)的鎖定和解鎖方法可以任意組合使用灭袁。例如猬错,您可以將鎖定消息與unlockWithCondition配對,或?qū)ockWhenCondition:消息與解鎖配對茸歧。當(dāng)然倦炒,后一種組合解鎖了鎖,但可能不會釋放等待特定條件值的任何線程软瞎。
下面顯示了如何使用條件鎖來處理生產(chǎn)者 - 消費者問題逢唤。想象一下,應(yīng)用程序包含一個數(shù)據(jù)隊列涤浇。生產(chǎn)者線程將數(shù)據(jù)添加到隊列中鳖藕,消費者線程從隊列中提取數(shù)據(jù)。生產(chǎn)者不需要等待特定條件只锭,但它必須等待鎖定可用著恩,以便它可以安全地將數(shù)據(jù)添加到隊列中
id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
while(true){
? ??[condLock lock];
? ??/* Add data to the queue. */
? ??[condLock unlockWithCondition:HAS_DATA];
}
????????由于鎖的初始條件設(shè)置為NO_DATA,因此生產(chǎn)者線程在初始獲取鎖時應(yīng)該沒有問題。 它用數(shù)據(jù)填充隊列并將條件設(shè)置為HAS_DATA喉誊。 在隨后的迭代過程中邀摆,生產(chǎn)者線程可以在它到達時添加新數(shù)據(jù),而不管隊列是空的還是仍然有一些數(shù)據(jù)裹驰。 唯一阻塞的時間是消費者線程正在從隊列中提取數(shù)據(jù)隧熙。
????????因為消費者線程必須有數(shù)據(jù)要處理片挂,所以它使用特定的條件在隊列上等待幻林。 當(dāng)生產(chǎn)者將數(shù)據(jù)放在隊列中時,消費者線程喚醒并獲取其鎖音念。 然后它可以從隊列中提取一些數(shù)據(jù)并更新隊列狀態(tài)沪饺。 下面顯示了使用者線程的處理循環(huán)的基本結(jié)構(gòu)。
while (true){
? ??[condLock lockWhenCondition:HAS_DATA];
? ??/* Remove data from the queue. */
? ??[condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)];
? ??// Process the data locally.
}
4.3闷愤、使用NSDistributedLock(分布式鎖)對象
????????NSDistributedLock類可以被多個主機上的多個應(yīng)用程序使用整葡,以限制對某些共享資源(如文件)的訪問。鎖本身實際上是使用文件系統(tǒng)項目(如文件或目錄)實現(xiàn)的互斥鎖讥脐。要使NSDistributedLock對象可用遭居,鎖定必須可供所有使用它的應(yīng)用程序?qū)懭搿_@通常意味著將其放在所有運行應(yīng)用程序的計算機都可訪問的文件系統(tǒng)上旬渠。
????????與其他類型的鎖不同俱萍,NSDistributedLock不符合NSLocking協(xié)議,因此沒有鎖定方法告丢。鎖定方法會阻止線程的執(zhí)行枪蘑,并要求系統(tǒng)以預(yù)定的速率輪詢鎖定。 NSDistributedLock提供了一個tryLock方法岖免,并且可以決定是否輪詢岳颇,而不是在代碼中施加處罰。
????????由于它是使用文件系統(tǒng)實現(xiàn)的颅湘,因此除非所有者明確釋放它话侧,否則不會釋放NSDistributedLock對象。如果應(yīng)用程序在持有分布式鎖的同時崩潰闯参,則其他客戶端將無法訪問受保護的資源掂摔。在這種情況下,您可以使用breakLock方法來分解現(xiàn)有的鎖赢赊,以便獲取它乙漓。但是,通常應(yīng)該避免打破鎖定释移,除非確定持有的進程已經(jīng)死亡并且無法釋放鎖定叭披。
????????與其他類型的鎖一樣,當(dāng)完成使用NSDistributedLock對象時,通過調(diào)用unlock方法釋放它涩蜘。
七嚼贡、使用條件
????????條件是一種特殊類型的鎖,可以使用它來同步操作執(zhí)行的順序同诫。 它們以微妙的方式不同于互斥鎖粤策。 一個等待條件的線程將一直處于阻塞狀態(tài),直到該條件由另一個線程顯式指示误窖。
????????由于實現(xiàn)操作系統(tǒng)所涉及的細微之處叮盘,即使條件鎖實際上沒有被代碼實際發(fā)送信號,也允許條件鎖返回一個虛假信號霹俺。 為了避免由這些虛假信號引起的問題柔吼,應(yīng)該始終將謂詞與條件鎖一起使用。 謂詞是確定線程是否安全進行的更具體的方法丙唧。 條件只是保持你的線程休眠愈魏,直到謂詞可以由信號線程設(shè)置。
下面展示如何在代碼中使用條件想际。
1培漏、使用NSCondition類
????????NSCondition類提供與POSIX條件相同的語義,但將所需的鎖和條件數(shù)據(jù)結(jié)構(gòu)封裝在單個對象中胡本。 結(jié)果NSCondition就成為了一個可以像互斥鎖一樣鎖定牌柄,然后像條件一樣等待的對象。
????????下面顯示了一段代碼片段打瘪,演示了在NSCondition對象上等待的事件序列友鼻。 cocoaCondition變量包含一個NSCondition對象,timeToDoWork變量是一個從另一個線程發(fā)送信號之前立即遞增的整數(shù)闺骚。
[cocoaCondition lock];
while (timeToDoWork <= 0)
[cocoaCondition wait];
timeToDoWork--;
// Do real work here.
[cocoaCondition unlock];
????????下面代碼用來表示Cocoa條件并遞增謂詞變量的代碼彩扔。在發(fā)出信號之前,應(yīng)該始終鎖定狀態(tài)僻爽。
[cocoaCondition lock];
timeToDoWork++;
[cocoaCondition signal];
[cocoaCondition unlock];
2虫碉、使用POSIX條件
????????POSIX線程條件鎖需要同時使用條件數(shù)據(jù)結(jié)構(gòu)和互斥鎖。 雖然兩個鎖結(jié)構(gòu)是分開的胸梆,但互斥鎖在運行時與條件結(jié)構(gòu)緊密相連敦捧。 等待信號的線程應(yīng)始終使用相同的互斥鎖和條件結(jié)構(gòu)。 而更改配對可能會導(dǎo)致錯誤碰镜。
下面顯示了條件和謂詞的基本初始化和用法兢卵。 初始化條件和互斥鎖后,等待線程使用ready_to_go變量作為謂詞進入while循環(huán)绪颖。 只有當(dāng)謂詞被設(shè)置并且條件隨后發(fā)出時秽荤,等待線程才會喚醒并開始工作。
pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean? ? ready_to_go = true;
void MyCondInitFunction()
{
? ??pthread_mutex_init(&mutex);
? ??pthread_cond_init(&condition, NULL);
}
void MyWaitOnConditionFunction()
{
? ??// Lock the mutex.
? ??pthread_mutex_lock(&mutex);
? ??// If the predicate is already set, then the while loop is bypassed;
? ??// otherwise, the thread sleeps until the predicate is set.
? ??while(ready_to_go == false){
? ??????pthread_cond_wait(&condition, &mutex);
? ??}
? ??// Do work. (The mutex should stay locked.)
? ??// Reset the predicate and release the mutex.
? ??ready_to_go = false;
? ??pthread_mutex_unlock(&mutex);
}
????????信號線程負(fù)責(zé)設(shè)置謂詞并將信號發(fā)送到條件鎖定。 下面顯示了實現(xiàn)這種行為的代碼窃款。 在這個例子中课兄,該條件在互斥體內(nèi)部發(fā)出信號,以防止在等待條件的線程之間發(fā)生競爭條件晨继。
void SignalThreadUsingCondition()
{
? ??// At this point, there should be work for the other thread to do.
? ??pthread_mutex_lock(&mutex);
? ??ready_to_go = true;
? ??// Signal the other thread to begin work.
? ??pthread_cond_signal(&condition);
? ??pthread_mutex_unlock(&mutex);
}
注意:上面的代碼是一個簡化的示例烟阐,用于顯示POSIX線程條件函數(shù)的基本用法。 實際的代碼應(yīng)該檢查這些函數(shù)返回的錯誤代碼并適當(dāng)?shù)靥幚硭鼈儭?/p>