轉(zhuǎn)載:談 iOS 的鎖
又到了春天挪坑的季節(jié),想起多次被問(wèn)及到鎖的概念游桩,決定好好總結(jié)一番色迂。
翻看目前關(guān)于 iOS 開(kāi)發(fā)鎖的文章,大部分都起源于 ibireme 的?《不再安全的 OSSpinLock》坐漏,我在看文章的時(shí)候有一些疑惑。這次主要想解決這些疑問(wèn):
鎖是什么?為什么要有鎖弄砍?鎖的分類(lèi)問(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ě)的操作,可能出現(xiàn)意料之外的結(jié)果:程序執(zhí)行的順序會(huì)被打亂,可能造成提前釋放一個(gè)變量,計(jì)算結(jié)果錯(cuò)誤等情況睛藻。所以我們需要將線程不安全的代碼 “鎖” 起來(lái)启上。保證一段代碼或者多段代碼操作的原子性,保證多個(gè)線程對(duì)同一個(gè)數(shù)據(jù)的訪問(wèn)?同步 (Synchronization)店印。
上面提到了原子性碧绞,我馬上想到了屬性關(guān)鍵字里, atomic 的作用吱窝。設(shè)置 atomic 之后讥邻,默認(rèn)生成的 getter 和 setter 方法執(zhí)行是原子的迫靖。但是它只保證了自身的讀/寫(xiě)操作,卻不能說(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)行加鎖励幼。
需要注意的是汰寓,讀/寫(xiě)的操作都需要加鎖,不僅僅是對(duì)一段代碼加鎖苹粟。
鎖的分類(lèi)方式有滑,可以根據(jù)鎖的狀態(tài),鎖的特性等進(jìn)行不同的分類(lèi)嵌削,很多鎖之間其實(shí)并不是并列的關(guān)系草丧,而是一種鎖下的不同實(shí)現(xiàn)表窘。關(guān)于鎖的分類(lèi)偶妖,可以參考
Java中的鎖分類(lèi)?看一下螟蒸。
很多談?wù)撴i的文章,都會(huì)提到互斥鎖艇劫,自旋鎖吼驶。很少有提到它們的關(guān)系,其實(shí)自旋鎖店煞,也是互斥鎖的一種實(shí)現(xiàn)旨剥,而?spin lock和mutex?兩者都是為了解決某項(xiàng)資源的互斥使用,在任何時(shí)刻只能有一個(gè)保持者。
區(qū)別在于?spin lock和?mutex?調(diào)度機(jī)制上有所不同浅缸。
OSSpinLock 是一種自旋鎖。它的特點(diǎn)是在線程等待時(shí)會(huì)一直輪詢(xún)魄咕,處于忙等狀態(tài)衩椒。自旋鎖由此得名。
自旋鎖看起來(lái)是比較耗費(fèi) cpu 的哮兰,然而在互斥臨界區(qū)計(jì)算量較小的場(chǎng)景下毛萌,它的效率遠(yuǎn)高于其它的鎖。
因?yàn)樗且恢碧幱?running 狀態(tài)喝滞,減少了線程切換上下文的消耗阁将。
關(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í)倒置,又稱(chēng)優(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 使用 Z志群,而 B 并不需要 Z着绷,搶先獲得時(shí)間片執(zhí)行。C 由于沒(méi)有時(shí)間片锌云,無(wú)法執(zhí)行荠医。
這種情況造成 A 在 B 之后執(zhí)行,使得優(yōu)先級(jí)被倒置了。
而如果 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í)間片,變成遲遲完不成州泊,不釋放鎖的情況丧蘸。
關(guān)于優(yōu)先級(jí)反轉(zhuǎn)一般有以下三種解決方案
優(yōu)先級(jí)繼承,故名思義遥皂,是將占有鎖的線程優(yōu)先級(jí)力喷,繼承等待該鎖的線程高優(yōu)先級(jí)刽漂,如果存在多個(gè)線程等待,就取其中之一最高的優(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)行下去,從而釋放掉鎖椒丧。
為了幫助理解,要提一下有關(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)行僻族。這種行為就稱(chēng)為 “線程調(diào)度(Thread Schedule)”。
在線程調(diào)度中屡谐,線程至少擁有三種狀態(tài) : 運(yùn)行(Running),就緒(Ready),等待(Waiting)述么。
處于 Running 的線程擁有的執(zhí)行時(shí)間,稱(chēng)為 時(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 是 GCD 中同步的一種方式,與他相關(guān)的只有三個(gè)函數(shù)粱侣,一個(gè)是創(chuàng)建信號(hào)量羊壹,一個(gè)是等待信號(hào),一個(gè)是發(fā)送信號(hào)齐婴。
信號(hào)量中油猫,二元信號(hào)量,是一種最簡(jiǎn)單的鎖柠偶。只有兩種狀態(tài)情妖,占用和非占用。
信號(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 是一個(gè)遞歸鎖。
遞歸鎖也稱(chēng)為可重入鎖敢会≡颍互斥鎖可以分為非遞歸鎖/遞歸鎖兩種,主要區(qū)別在于:同一個(gè)線程可以重復(fù)獲取遞歸鎖鸥昏,不會(huì)死鎖; 同一個(gè)線程重復(fù)獲取非遞歸鎖塞俱,則會(huì)產(chǎn)生死鎖。
因?yàn)槭沁f歸鎖吏垮,我們可以寫(xiě)類(lèi)似這樣的代碼:
? - (void)testLock{
? if(_count>0){
? ? @synchronized (obj) {
? ? ? ? _count = _count - 1;
? ? ? ? [self testLock];
? ? ? }
? ? }
}
而如果換成 NSLock 障涯,它就會(huì)因?yàn)檫f歸發(fā)生死鎖了。
如果 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è)類(lèi)內(nèi)部維護(hù)的NSObject對(duì)象冯痢,而且這個(gè)對(duì)象是對(duì)外不可見(jiàn)的,不被隨便修改的氮昧。
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é)議類(lèi)型包括以下幾種:
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í)繼承類(lèi)型的互斥鎖填渠,它不是默認(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é)議類(lèi)型為?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 屬于 pthread_mutex 的一層封裝, 設(shè)置了屬性為?PTHREAD_MUTEX_ERRORCHECK?蒿秦。
它會(huì)損失一定性能換來(lái)錯(cuò)誤提示。并簡(jiǎn)化直接使用 pthread_mutex 的定義饺饭。
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)操作。
條件變量川尖,類(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)題,是一個(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)?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];
}
每個(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 稱(chēng)為條件鎖,只有 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ù)之間的依賴(lài).
NSRecursiveLock 和前面提到的 @synchonized 一樣,是一個(gè)遞歸鎖玷室。
NSRecursiveLock 與 NSLock 的區(qū)別在于內(nèi)部封裝的 pthread_mutex_t 對(duì)象的類(lèi)型不同零蓉,NSRecursiveLock?的類(lèi)型被設(shè)置為PTHREAD_MUTEX_RECURSIVE。
這里順帶提一下?NSDistributedLock, 是 macOS 下的一種鎖.
蘋(píng)果文檔?對(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 更類(lèi)似于文件鎖的概念。 有興趣的可以看一看?Linux 2.6 中的文件鎖
除了用鎖之外部蛇,有其它方法保證線程安全嗎撞反?
首先,盡量避免多線程的設(shè)計(jì)搪花。因?yàn)槎嗑€程訪問(wèn)會(huì)出現(xiàn)很多不可控制的情況遏片。有些情況即使上鎖嘹害,也無(wú)法保證百分之百的安全,例如自旋鎖的問(wèn)題吮便。
而如果還是得用多線程笔呀,那么避免對(duì)資源做修改。
如果都是訪問(wèn)共享資源髓需,而不去修改共享資源许师,也可以保證線程安全。
比如 NSArry 作為不可變類(lèi)是線程安全的僚匆。然而它們的可變版本微渠,比如 NSMutableArray 是線程不安全的。事實(shí)上咧擂,如果是在一個(gè)隊(duì)列中串行地進(jìn)行訪問(wèn)的話逞盆,在不同線程中使用它們也是沒(méi)有問(wèn)題的。
如果實(shí)在要使用多線程松申,也沒(méi)有必要過(guò)分追求效率云芦,而更多的考慮線程安全問(wèn)題,使用對(duì)應(yīng)的鎖贸桶。
對(duì)于平時(shí)編寫(xiě)應(yīng)用里的多線程代碼舅逸,還是建議用 @synchronized,NSLock 等皇筛,可讀性和安全性都好琉历,多線程安全比多線程性能更重要。
這里提供了我學(xué)習(xí)鎖用的代碼水醋,感興趣的可以看一看?實(shí)驗(yàn) Demo
Objective-C中不同方式實(shí)現(xiàn)鎖(一)