轉(zhuǎn)發(fā):LockForiOS
又到了春天挪坑的季節(jié)钮莲,想起多次被問(wèn)及到鎖的概念夹抗,決定好好總結(jié)一番蒲祈。
翻看目前關(guān)于 iOS 開(kāi)發(fā)鎖的文章呆瞻,大部分都起源于 ibireme 的 《不再安全的 OSSpinLock》谷婆,我在看文章的時(shí)候有一些疑惑路召。這次主要想解決這些疑問(wèn):
- 鎖是什么?
- 為什么要有鎖?
- 鎖的分類問(wèn)題
- 為什么 OSSpinLock 不安全波材?
- 解決自旋鎖不安全問(wèn)題有幾種方式
- 為什么換用其它的鎖股淡,可以解決 OSSpinLock 的問(wèn)題?
- 自旋鎖和互斥鎖的關(guān)系是平行對(duì)立的嗎廷区?
- 信號(hào)量和互斥量的關(guān)系
- 信號(hào)量和條件變量的區(qū)別
鎖是什么
鎖 -- 是保證線程安全常見(jiàn)的同步工具唯灵。鎖是一種非強(qiáng)制的機(jī)制,每一個(gè)線程在訪問(wèn)數(shù)據(jù)或者資源前隙轻,要先獲取(Acquire) 鎖埠帕,并在訪問(wèn)結(jié)束之后釋放(Release)鎖。如果鎖已經(jīng)被占用玖绿,其它試圖獲取鎖的線程會(huì)等待敛瓷,直到鎖重新可用。
為什么要有鎖斑匪?
前面說(shuō)到了呐籽,鎖是用來(lái)保護(hù)線程安全的工具。
可以試想一下,多線程編程時(shí)狡蝶,沒(méi)有鎖的情況 -- 也就是線程不安全庶橱。
當(dāng)多個(gè)線程同時(shí)對(duì)一塊內(nèi)存發(fā)生讀和寫的操作,可能出現(xiàn)意料之外的結(jié)果:
程序執(zhí)行的順序會(huì)被打亂,可能造成提前釋放一個(gè)變量,計(jì)算結(jié)果錯(cuò)誤等情況贪惹。
所以我們需要將線程不安全的代碼 “鎖” 起來(lái)苏章。保證一段代碼或者多段代碼操作的原子性,保證多個(gè)線程對(duì)同一個(gè)數(shù)據(jù)的訪問(wèn) 同步 (Synchronization)奏瞬。
屬性設(shè)置 atomic
上面提到了原子性枫绅,我馬上想到了屬性關(guān)鍵字里, atomic 的作用硼端。
設(shè)置 atomic 之后撑瞧,默認(rèn)生成的 getter 和 setter 方法執(zhí)行是原子的。
但是它只保證了自身的讀/寫操作显蝌,卻不能說(shuō)是線程安全预伺。
如下情況:
//thread A
for (int i = 0; i < 100000; i ++) {
if (i % 2 == 0) {
self.arr = @[@"1", @"2", @"3"];
}else {
self.arr = @[@"1"];
}
NSLog(@"Thread A: %@\n", self.arr);
}
//thread B
if (self.arr.count >= 2) {
NSString* str = [self.arr objectAtIndex:1];
}
就算在 thread B 中針對(duì) arr 數(shù)組進(jìn)行了大小判斷,但是仍然可能在 objectAtIndex:
操作時(shí)被改變數(shù)組長(zhǎng)度曼尊,導(dǎo)致出錯(cuò)酬诀。這種情況聲明為 atomic 也沒(méi)有用。
而解決方式骆撇,就是進(jìn)行加鎖瞒御。
需要注意的是,讀/寫的操作都需要加鎖神郊,不僅僅是對(duì)一段代碼加鎖肴裙。
鎖的分類
鎖的分類方式,可以根據(jù)鎖的狀態(tài)涌乳,鎖的特性等進(jìn)行不同的分類蜻懦,很多鎖之間其實(shí)并不是并列的關(guān)系,而是一種鎖下的不同實(shí)現(xiàn)夕晓。關(guān)于鎖的分類宛乃,可以參考 Java中的鎖分類 看一下。
自旋鎖和互斥鎖的關(guān)系
很多談?wù)撴i的文章蒸辆,都會(huì)提到互斥鎖征炼,自旋鎖。很少有提到它們的關(guān)系躬贡,其實(shí)自旋鎖谆奥,也是互斥鎖的一種實(shí)現(xiàn),而 spin lock
和 mutex
兩者都是為了解決某項(xiàng)資源的互斥使用,在任何時(shí)刻只能有一個(gè)保持者拂玻。
區(qū)別在于 spin lock
和 mutex
調(diào)度機(jī)制上有所不同酸些。
OSSpinLock
OSSpinLock 是一種自旋鎖宰译。它的特點(diǎn)是在線程等待時(shí)會(huì)一直輪詢,處于忙等狀態(tài)擂仍。自旋鎖由此得名。
自旋鎖看起來(lái)是比較耗費(fèi) cpu 的熬甚,然而在互斥臨界區(qū)計(jì)算量較小的場(chǎng)景下逢渔,它的效率遠(yuǎn)高于其它的鎖。
因?yàn)樗且恢碧幱?running 狀態(tài)乡括,減少了線程切換上下文的消耗肃廓。
為什么 OSSpinLock 不再安全?
關(guān)于 OSSpinLock 不再安全诲泌,原因就在于優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題盲赊。
優(yōu)先級(jí)反轉(zhuǎn)(Priority Inversion)
什么情況叫做優(yōu)先級(jí)反轉(zhuǎn)?
wikipedia 上是這么定義的:
優(yōu)先級(jí)倒置敷扫,又稱優(yōu)先級(jí)反轉(zhuǎn)哀蘑、優(yōu)先級(jí)逆轉(zhuǎn)、優(yōu)先級(jí)翻轉(zhuǎn)葵第,是一種不希望發(fā)生的任務(wù)調(diào)度狀態(tài)绘迁。在該種狀態(tài)下,一個(gè)高優(yōu)先級(jí)任務(wù)間接被一個(gè)低優(yōu)先級(jí)任務(wù)所搶先(preemtped)卒密,使得兩個(gè)任務(wù)的相對(duì)優(yōu)先級(jí)被倒置缀台。 這往往出現(xiàn)在一個(gè)高優(yōu)先級(jí)任務(wù)等待訪問(wèn)一個(gè)被低優(yōu)先級(jí)任務(wù)正在使用的臨界資源,從而阻塞了高優(yōu)先級(jí)任務(wù)哮奇;同時(shí)膛腐,該低優(yōu)先級(jí)任務(wù)被一個(gè)次高優(yōu)先級(jí)的任務(wù)所搶先,從而無(wú)法及時(shí)地釋放該臨界資源鼎俘。這種情況下哲身,該次高優(yōu)先級(jí)任務(wù)獲得執(zhí)行權(quán)。
再消化一下
有:高優(yōu)先級(jí)任務(wù)A / 次高優(yōu)先級(jí)任務(wù)B / 低優(yōu)先級(jí)任務(wù)C / 資源Z 贸伐。
A 等待 C 執(zhí)行后的 Z
律罢,
而 B 并不需要 Z,搶先獲得時(shí)間片執(zhí)行
棍丐。
C 由于沒(méi)有時(shí)間片误辑,無(wú)法執(zhí)行
(優(yōu)先級(jí)相對(duì)沒(méi)有B高)。
這種情況造成 A 在C 之后執(zhí)行,C在B之后歌逢,間接的高優(yōu)先級(jí)A在次高優(yōu)先級(jí)任務(wù)B 之后執(zhí)行, 使得優(yōu)先級(jí)被倒置了巾钉。(假設(shè): A 等待資源時(shí)不是阻塞等待,而是忙循環(huán)秘案,則可能永遠(yuǎn)無(wú)法獲得資源砰苍。此時(shí) C 無(wú)法與 A 爭(zhēng)奪 CPU 時(shí)間潦匈,從而 C 無(wú)法執(zhí)行,進(jìn)而無(wú)法釋放資源赚导。造成的后果茬缩,就是 A 無(wú)法獲得 Z 而繼續(xù)推進(jìn)。)
而 OSSpinLock 忙等的機(jī)制吼旧,就可能造成高優(yōu)先級(jí)一直 running 凰锡,占用 cpu 時(shí)間片。而低優(yōu)先級(jí)任務(wù)無(wú)法搶占時(shí)間片圈暗,變成遲遲完不成掂为,不釋放鎖的情況。
優(yōu)先級(jí)反轉(zhuǎn)的解決方案
關(guān)于優(yōu)先級(jí)反轉(zhuǎn)一般有以下三種解決方案
優(yōu)先級(jí)繼承
優(yōu)先級(jí)繼承员串,故名思義勇哗,是將占有鎖的線程優(yōu)先級(jí),繼承等待該鎖的線程高優(yōu)先級(jí)寸齐,如果存在多個(gè)線程等待欲诺,就取其中之一最高的優(yōu)先級(jí)繼承。
優(yōu)先級(jí)天花板
優(yōu)先級(jí)天花板渺鹦,則是直接設(shè)置優(yōu)先級(jí)上限瞧栗,給臨界區(qū)一個(gè)最高優(yōu)先級(jí),進(jìn)入臨界區(qū)的進(jìn)程都將獲得這個(gè)高優(yōu)先級(jí)海铆。
如果其他試圖進(jìn)入臨界區(qū)的進(jìn)程的優(yōu)先級(jí)迹恐,都低于這個(gè)最高優(yōu)先級(jí),那么優(yōu)先級(jí)反轉(zhuǎn)就不會(huì)發(fā)生卧斟。
禁止中斷
禁止中斷的特點(diǎn)殴边,在于任務(wù)只存在兩種優(yōu)先級(jí):可被搶占的 / 禁止中斷的 。
前者為一般任務(wù)運(yùn)行時(shí)的優(yōu)先級(jí)珍语,后者為進(jìn)入臨界區(qū)的優(yōu)先級(jí)锤岸。
通過(guò)禁止中斷來(lái)保護(hù)臨界區(qū),沒(méi)有其它第三種的優(yōu)先級(jí)板乙,也就不可能發(fā)生反轉(zhuǎn)了是偷。
為什么使用其它的鎖,可以解決優(yōu)先級(jí)反轉(zhuǎn)募逞?
我們看到很多本來(lái)使用 OSSpinLock 的知名項(xiàng)目蛋铆,都改用了其它方式替代,比如 pthread_mutex
和 dispatch_semaphore
放接。
那為什么其它的鎖刺啦,就不會(huì)有優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題呢?如果按照上面的想法纠脾,其它鎖也可能出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)玛瘸。
原因在于蜕青,其它鎖出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)后,高優(yōu)先級(jí)的任務(wù)不會(huì)忙等糊渊。因?yàn)樘幱诘却隣顟B(tài)的高優(yōu)先級(jí)任務(wù)右核,沒(méi)有占用時(shí)間片,所以低優(yōu)先級(jí)任務(wù)一般都能進(jìn)行下去渺绒,從而釋放掉鎖贺喝。
線程調(diào)度
為了幫助理解,要提一下有關(guān)線程調(diào)度
的概念芒篷。
無(wú)論多核心還是單核搜变,我們的線程運(yùn)行總是 "并發(fā)" 的采缚。
當(dāng) cpu 數(shù)量大于等于線程數(shù)量针炉,這個(gè)時(shí)候是真正并發(fā),可以多個(gè)線程同時(shí)執(zhí)行計(jì)算扳抽。
當(dāng) cpu 數(shù)量小于線程數(shù)量篡帕,總有一個(gè) cpu 會(huì)運(yùn)行多個(gè)線程,這時(shí)候"并發(fā)"就是一種模擬出來(lái)的狀態(tài)贸呢。操作系統(tǒng)通過(guò)不斷的切換線程镰烧,每個(gè)線程執(zhí)行一小段時(shí)間,讓多個(gè)線程看起來(lái)就像在同時(shí)運(yùn)行楞陷。這種行為就稱為 "線程調(diào)度(Thread Schedule)"
怔鳖。
線程狀態(tài)
在線程調(diào)度中,線程至少擁有三種狀態(tài) : 運(yùn)行(Running)
,就緒(Ready)
,等待(Waiting)
固蛾。
處于 Running
的線程擁有的執(zhí)行時(shí)間结执,稱為 時(shí)間片(Time Slice)
,時(shí)間片 用完時(shí)艾凯,進(jìn)入Ready
狀態(tài)献幔。如果在Running
狀態(tài),時(shí)間片沒(méi)有用完趾诗,就開(kāi)始等待某一個(gè)事件(通常是 IO 或 同步 )蜡感,則進(jìn)入Waiting
狀態(tài)。
如果有線程從Running
狀態(tài)離開(kāi)恃泪,調(diào)度系統(tǒng)就會(huì)選擇一個(gè)Ready
的線程進(jìn)入 Running
狀態(tài)郑兴。而Waiting
的線程等待的事件完成后,就會(huì)進(jìn)入Ready
狀態(tài)贝乎。
dispatch_semaphore
dispatch_semaphore
是 GCD 中同步的一種方式杈笔,與他相關(guān)的只有三個(gè)函數(shù),一個(gè)是創(chuàng)建信號(hào)量糕非,一個(gè)是等待信號(hào)蒙具,一個(gè)是發(fā)送信號(hào)球榆。
信號(hào)量機(jī)制
信號(hào)量中,二元信號(hào)量禁筏,是一種最簡(jiǎn)單的鎖持钉。只有兩種狀態(tài),占用和非占用篱昔。二元信號(hào)量適合唯一一個(gè)線程獨(dú)占訪問(wèn)的資源每强。而多元信號(hào)量簡(jiǎn)稱 信號(hào)量(Semaphore)。
信號(hào)量和互斥量的區(qū)別
信號(hào)量是允許并發(fā)訪問(wèn)的州刽,也就是說(shuō)空执,允許多個(gè)線程同時(shí)執(zhí)行多個(gè)任務(wù)。信號(hào)量可以由一個(gè)線程獲取穗椅,然后由不同的線程釋放辨绊。
互斥量只允許一個(gè)線程同時(shí)執(zhí)行一個(gè)任務(wù)。也就是同一個(gè)程獲取匹表,同一個(gè)線程釋放门坷。
之前我對(duì),互斥量只由一個(gè)線程獲取和釋放袍镀,理解的比較狹義默蚌,以為這里的獲取和釋放,是系統(tǒng)強(qiáng)制要求的苇羡,用 NSLock
實(shí)驗(yàn)發(fā)現(xiàn)它可以在不同線程獲取和釋放绸吸,感覺(jué)很疑惑。
實(shí)際上设江,的確能在不同線程獲取/釋放同一個(gè)互斥鎖锦茁,但互斥鎖本來(lái)就用于同一個(gè)線程中上鎖和解鎖。這里的意義更多在于代碼使用的層面绣硝。
關(guān)鍵在于蜻势,理解信號(hào)量可以允許 N 個(gè)信號(hào)量允許 N 個(gè)線程并發(fā)地執(zhí)行任務(wù)。
@synchonized
@synchonized 是一個(gè)遞歸鎖鹉胖。
遞歸鎖
遞歸鎖也稱為可重入鎖
握玛。互斥鎖可以分為非遞歸鎖/遞歸鎖
兩種甫菠,主要區(qū)別在于:同一個(gè)線程可以重復(fù)獲取遞歸鎖挠铲,不會(huì)死鎖; 同一個(gè)線程重復(fù)獲取非遞歸鎖,則會(huì)產(chǎn)生死鎖寂诱。
因?yàn)槭沁f歸鎖拂苹,我們可以寫類似這樣的代碼:
- (void)testLock{
if(_count>0){
@synchronized (obj) {
_count = _count - 1;
[self testLock];
}
}
}
而如果換成NSLock
,它就會(huì)因?yàn)檫f歸發(fā)生死鎖了痰洒。
實(shí)際使用問(wèn)題
如果obj 為 nil
,或者 obj
地址不同瓢棒,鎖會(huì)失效浴韭。
所以我們要防止如下的情況:
@synchronized (obj) {
obj = newObj;
}
這里的 obj 被更改后,等到其它線程訪問(wèn)時(shí)脯宿,就和沒(méi)加鎖一樣直接進(jìn)去了念颈。
另外一種情況,就是 @synchonized(self)
. 不少代碼都是直接將self傳入@synchronized當(dāng)中连霉,而 self
很容易作為一個(gè)外部對(duì)象榴芳,被調(diào)用和修改。所以它和上面是一樣的情況跺撼,需要避免使用窟感。
正確的做法是什么?obj 應(yīng)當(dāng)傳入一個(gè)類內(nèi)部維護(hù)的NSObject
對(duì)象歉井,而且這個(gè)對(duì)象是對(duì)外不可見(jiàn)的,不被隨便修改的柿祈。
pthread_mutex
pthread
定義了一組跨平臺(tái)的線程相關(guān)的 API,其中可以使用 pthread_mutex
作為互斥鎖酣难。
pthread_mutex
不是使用忙等谍夭,而是同信號(hào)量一樣黑滴,會(huì)阻塞線程并進(jìn)行等待憨募,調(diào)用時(shí)進(jìn)行線程上下文切換。
pthread_mutex` 本身?yè)碛性O(shè)置協(xié)議的功能袁辈,通過(guò)設(shè)置它的協(xié)議菜谣,來(lái)解決優(yōu)先級(jí)反轉(zhuǎn):
pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol)
其中協(xié)議類型包括以下幾種:
-
PTHREAD_PRIO_NONE
:線程的優(yōu)先級(jí)和調(diào)度不會(huì)受到互斥鎖擁有權(quán)的影響。 -
PTHREAD_PRIO_INHERIT
:當(dāng)高優(yōu)先級(jí)的等待低優(yōu)先級(jí)的線程鎖定互斥量時(shí)晚缩,低優(yōu)先級(jí)的線程以高優(yōu)先級(jí)線程的優(yōu)先級(jí)運(yùn)行尾膊。這種方式將以繼承的形式傳遞。當(dāng)線程解鎖互斥量時(shí)荞彼,線程的優(yōu)先級(jí)自動(dòng)被降到它原來(lái)的優(yōu)先級(jí)冈敛。該協(xié)議就是支持優(yōu)先級(jí)繼承類型的互斥鎖,它不是默認(rèn)選項(xiàng)鸣皂,需要在程序中進(jìn)行設(shè)置抓谴。 -
PTHREAD_PRIO_PROTECT
:當(dāng)線程擁有一個(gè)或多個(gè)使用PTHREAD_PRIO_PROTECT
初始化的互斥鎖時(shí),此協(xié)議值會(huì)影響其他線程(如thrd2
)的優(yōu)先級(jí)和調(diào)度寞缝。thrd2
以其較高的優(yōu)先級(jí)或者以thrd2
擁有的所有互斥鎖的最高優(yōu)先級(jí)上限運(yùn)行癌压。基于被thrd2
擁有的任一互斥鎖阻塞的較高優(yōu)先級(jí)線程對(duì)于thrd2
的調(diào)度沒(méi)有任何影響荆陆。
設(shè)置協(xié)議類型為 PTHREAD_PRIO_INHERIT
滩届,運(yùn)用優(yōu)先級(jí)繼承的方式,可以解決優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題被啼。
而我們?cè)?iOS 中使用的 NSLock,NSRecursiveLock
等都是基于pthread_mutex
做實(shí)現(xiàn)的帜消。
NSLock
NSLock
屬于 pthread_mutex
的一層封裝, 設(shè)置了屬性為 PTHREAD_MUTEX_ERRORCHECK
棠枉。
它會(huì)損失一定性能換來(lái)錯(cuò)誤提示。并簡(jiǎn)化直接使用 pthread_mutex
的定義泡挺。
NSCondition
NSCondition
是通過(guò)pthread
中的條件變量(condition variable) pthread_cond_t
來(lái)實(shí)現(xiàn)的术健。
條件變量
在線程間的同步中,有這樣一種情況: 線程 A 需要等條件 C 成立,才能繼續(xù)往下執(zhí)行.現(xiàn)在這個(gè)條件不成立粘衬,線程 A 就阻塞等待. 而線程 B 在執(zhí)行過(guò)程中荞估,使條件 C 成立了,就喚醒線程 A 繼續(xù)執(zhí)行稚新。
對(duì)于上述情況勘伺,可以使用條件變量來(lái)操作。
條件變量褂删,類似信號(hào)量飞醉,提供線程阻塞與信號(hào)機(jī)制,可以用來(lái)阻塞某個(gè)線程屯阀,等待某個(gè)數(shù)據(jù)就緒后缅帘,隨后喚醒線程。
一個(gè)條件變量總是和一個(gè)互斥量搭配使用难衰。
而NSCondition
其實(shí)就是封裝了一個(gè)互斥鎖和條件變量钦无,互斥鎖的lock/unlock
方法和后者的wait/signal
統(tǒng)一封裝在 NSCondition
對(duì)象中,暴露給使用者盖袭。
用條件變量控制線程同步失暂,最為經(jīng)典的例子就是 生產(chǎn)者-消費(fèi)者問(wèn)題。
生產(chǎn)者-消費(fèi)者問(wèn)題
生產(chǎn)者消費(fèi)者問(wèn)題,是一個(gè)著名的線程同步問(wèn)題鳄虱,該問(wèn)題描述如下:
有一個(gè)生產(chǎn)者在生產(chǎn)產(chǎn)品弟塞,這些產(chǎn)品將提供給若干個(gè)消費(fèi)者去消費(fèi)。要求讓生產(chǎn)者和消費(fèi)者能并發(fā)執(zhí)行拙已,在兩者之間設(shè)置一個(gè)具有多個(gè)緩沖區(qū)的緩沖池决记,生產(chǎn)者將它生產(chǎn)的產(chǎn)品放入一個(gè)緩沖區(qū)中,消費(fèi)者可以從緩沖區(qū)中取走產(chǎn)品進(jìn)行消費(fèi)倍踪,顯然生產(chǎn)者和消費(fèi)者之間必須保持同步系宫,即不允許消費(fèi)者到一個(gè)空的緩沖區(qū)中取產(chǎn)品,也不允許生產(chǎn)者向一個(gè)已經(jīng)放入產(chǎn)品的緩沖區(qū)中再次投放產(chǎn)品惭适。
我們可以剛好可以使用 NSCondition
解決生產(chǎn)者-消費(fèi)者問(wèn)題笙瑟。具體的代碼放置在文末的 Demo 里了。
這里需要注意癞志,實(shí)際操作NSCondition
做 wait
操作時(shí),如果用if
判斷:
if(count==0){
[condition wait];
}
上面這樣是不能保證消費(fèi)者是線程安全的往枷。
因?yàn)?code>NSCondition可以給每個(gè)線程分別加鎖,但加鎖后不影響其他線程進(jìn)入臨界區(qū)。所以 NSCondition
使用 wait
并加鎖后错洁,并不能真正保證線程的安全秉宿。
當(dāng)一個(gè)signal
操作發(fā)出時(shí),如果有兩個(gè)線程都在做 消費(fèi)者 操作屯碴,那同時(shí)都會(huì)消耗掉資源描睦,于是繞過(guò)了檢查。
例如我們的條件是导而,count == 0
執(zhí)行等待忱叭。
假設(shè)當(dāng)前 count = 0
,線程A 要判斷到 count == 0
,執(zhí)行等待;
線程B 執(zhí)行了count = 1
今艺,并喚醒線程A 執(zhí)行 count - 1
韵丑,同時(shí)線程C 也判斷到 count > 0
。因?yàn)樘幵诓煌木€程鎖虚缎,同樣判斷執(zhí)行了 count - 1
撵彻。2 個(gè)線程都會(huì)執(zhí)行count - 1
,但是 count = 1
,實(shí)際就出現(xiàn)count = -1
的情況实牡。
所以為了保證消費(fèi)者操作的正確陌僵,使用 while 循環(huán)中的判斷,進(jìn)行二次確認(rèn):
while (count == 0) {
[condition wait];
}
條件變量和信號(hào)量的區(qū)別
每個(gè)信號(hào)量有一個(gè)與之關(guān)聯(lián)的值,發(fā)出時(shí)+1创坞,等待時(shí)-1碗短,任何線程都可以發(fā)出一個(gè)信號(hào),即使沒(méi)有線程在等待該信號(hào)量的值摆霉。
可是對(duì)于條件變量豪椿,例如 pthread_cond_signal
發(fā)出信號(hào)后奔坟,沒(méi)有任何線程阻塞在 pthread_cond_wait
上携栋,那這個(gè)條件變量上的信號(hào)會(huì)直接丟失掉。
NSConditionLock
NSConditionLock
稱為條件鎖咳秉,只有 condition
參數(shù)與初始化時(shí)候的 condition
相等婉支,lock
才能正確進(jìn)行加鎖操作。
這里分清兩個(gè)概念:
-
unlockWithCondition:
,它是先解鎖澜建,再修改 condition 參數(shù)的值向挖。 并不是當(dāng) condition 符合某個(gè)件值去解鎖。 -
lockWhenCondition:
,它與unlockWithCondition:
不一樣炕舵,不會(huì)修改 condition 參數(shù)的值何之,而是符合 condition 的值再上鎖。
在這里可以利用 NSConditionLock
實(shí)現(xiàn)任務(wù)之間的依賴.
NSRecursiveLock
NSRecursiveLock
和前面提到的 @synchonized
一樣咽筋,是一個(gè)遞歸鎖溶推。
NSRecursiveLock
與 NSLock
的區(qū)別在于內(nèi)部封裝的pthread_mutex_t
對(duì)象的類型不同,NSRecursiveLock
的類型被設(shè)置為 PTHREAD_MUTEX_RECURSIVE
。
NSDistributedLock
這里順帶提一下 NSDistributedLock
, 是 macOS 下的一種鎖.
蘋果文檔 對(duì)于NSDistributedLock 的描述是:
A lock that multiple applications on multiple hosts can use to restrict access to some shared resource, such as a file
意思是說(shuō)蒜危,它是一個(gè)用在多個(gè)主機(jī)間的多應(yīng)用的鎖虱痕,可以限制訪問(wèn)一些共享資源,例如文件辐赞。
按字面意思翻譯部翘,NSDistributedLock
應(yīng)該就叫做 分布式鎖。但是看概念和資料响委,在 解決NSDistributedLock進(jìn)程互斥鎖的死鎖問(wèn)題(一) 里面看到新思,NSDistributedLock 更類似于文件鎖的概念。 有興趣的可以看一看 Linux 2.6 中的文件鎖
其它保證線程安全的方式
除了用鎖之外赘风,有其它方法保證線程安全嗎表牢?
使用單線程訪問(wèn)
首先,盡量避免多線程的設(shè)計(jì)贝次。因?yàn)槎嗑€程訪問(wèn)會(huì)出現(xiàn)很多不可控制的情況崔兴。有些情況即使上鎖,也無(wú)法保證百分之百的安全蛔翅,例如自旋鎖的問(wèn)題敲茄。
不對(duì)資源做修改
而如果還是得用多線程,那么避免對(duì)資源做修改山析。
如果都是訪問(wèn)共享資源堰燎,而不去修改共享資源,也可以保證線程安全笋轨。
比如NSArry
作為不可變類是線程安全的秆剪。然而它們的可變版本,比如 NSMutableArray
是線程不安全的爵政。事實(shí)上仅讽,如果是在一個(gè)隊(duì)列中串行地進(jìn)行訪問(wèn)的話,在不同線程中使用它們也是沒(méi)有問(wèn)題的钾挟。
總結(jié)
如果實(shí)在要使用多線程洁灵,也沒(méi)有必要過(guò)分追求效率,而更多的考慮線程安全問(wèn)題掺出,使用對(duì)應(yīng)的鎖徽千。
對(duì)于平時(shí)編寫應(yīng)用里的多線程代碼,還是建議用 @synchronized汤锨,NSLock
等双抽,可讀性和安全性都好,多線程安全比多線程性能更重要闲礼。
這里提供了我學(xué)習(xí)鎖用的代碼牍汹,感興趣的可以看一看 實(shí)驗(yàn) Demo
參考
Objective-C中不同方式實(shí)現(xiàn)鎖(一)