談 iOS 你能打開(kāi)幾把鎖

轉(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)店印。

屬性設(shè)置 atomic

上面提到了原子性碧绞,我馬上想到了屬性關(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)

鎖的分類(lèi)方式有滑,可以根據(jù)鎖的狀態(tài),鎖的特性等進(jìn)行不同的分類(lèi)嵌削,很多鎖之間其實(shí)并不是并列的關(guān)系草丧,而是一種鎖下的不同實(shí)現(xiàn)表窘。關(guān)于鎖的分類(lèi)偶妖,可以參考

Java中的鎖分類(lèi)?看一下螟蒸。

自旋鎖和互斥鎖的關(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ì)一直輪詢(xún)魄咕,處于忙等狀態(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í)倒置,又稱(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í)間片,變成遲遲完不成州泊,不釋放鎖的情況丧蘸。

優(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)行僻族。這種行為就稱(chēng)為 “線程調(diào)度(Thread Schedule)”。

線程狀態(tài)

在線程調(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

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)量和互斥量的區(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è)遞歸鎖。

遞歸鎖

遞歸鎖也稱(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ā)生死鎖了。

實(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è)類(lèi)內(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é)議類(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

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)操作。

條件變量川尖,類(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)?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 稱(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

NSRecursiveLock 和前面提到的 @synchonized 一樣,是一個(gè)遞歸鎖玷室。

NSRecursiveLock 與 NSLock 的區(qū)別在于內(nèi)部封裝的 pthread_mutex_t 對(duì)象的類(lèi)型不同零蓉,NSRecursiveLock?的類(lèi)型被設(shè)置為PTHREAD_MUTEX_RECURSIVE。

NSDistributedLock

這里順帶提一下?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 中的文件鎖

其它保證線程安全的方式

除了用鎖之外部蛇,有其它方法保證線程安全嗎撞反?

使用單線程訪問(wèn)

首先,盡量避免多線程的設(shè)計(jì)搪花。因?yàn)槎嗑€程訪問(wèn)會(huì)出現(xiàn)很多不可控制的情況遏片。有些情況即使上鎖嘹害,也無(wú)法保證百分之百的安全,例如自旋鎖的問(wèn)題吮便。

不對(duì)資源做修改

而如果還是得用多線程笔呀,那么避免對(duì)資源做修改。

如果都是訪問(wèn)共享資源髓需,而不去修改共享資源许师,也可以保證線程安全。

比如 NSArry 作為不可變類(lèi)是線程安全的僚匆。然而它們的可變版本微渠,比如 NSMutableArray 是線程不安全的。事實(shí)上咧擂,如果是在一個(gè)隊(duì)列中串行地進(jìn)行訪問(wèn)的話逞盆,在不同線程中使用它們也是沒(méi)有問(wèn)題的。

總結(jié)

如果實(shí)在要使用多線程松申,也沒(méi)有必要過(guò)分追求效率云芦,而更多的考慮線程安全問(wèn)題,使用對(duì)應(yīng)的鎖贸桶。

對(duì)于平時(shí)編寫(xiě)應(yīng)用里的多線程代碼舅逸,還是建議用 @synchronized,NSLock 等皇筛,可讀性和安全性都好琉历,多線程安全比多線程性能更重要。

這里提供了我學(xué)習(xí)鎖用的代碼水醋,感興趣的可以看一看?實(shí)驗(yàn) Demo

參考

程序員的自我修養(yǎng)

iOS多線程到底不安全在哪里善已?

幾個(gè)關(guān)于鎖的名詞聯(lián)系是什么

線程同步:互斥鎖,條件變量离例,信號(hào)量

Objective-C中不同方式實(shí)現(xiàn)鎖(一)

iOS多線程-各種線程鎖的簡(jiǎn)單介紹

深入理解 iOS 開(kāi)發(fā)中的鎖

關(guān)于 @synchronized换团,這兒比你想知道的還要多

linux c學(xué)習(xí)筆記—-互斥鎖屬性

正確使用多線程同步鎖@synchronized()

iOS 中幾種常用的鎖總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宫蛆,隨后出現(xiàn)的幾起案子艘包,更是在濱河造成了極大的恐慌,老刑警劉巖耀盗,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件想虎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡叛拷,警方通過(guò)查閱死者的電腦和手機(jī)舌厨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)忿薇,“玉大人裙椭,你說(shuō)我怎么就攤上這事躏哩。” “怎么了揉燃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵扫尺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我炊汤,道長(zhǎng)正驻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任抢腐,我火速辦了婚禮姑曙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迈倍。我一直安慰自己伤靠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布授瘦。 她就那樣靜靜地躺著,像睡著了一般竟宋。 火紅的嫁衣襯著肌膚如雪提完。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天丘侠,我揣著相機(jī)與錄音徒欣,去河邊找鬼。 笑死蜗字,一個(gè)胖子當(dāng)著我的面吹牛打肝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挪捕,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼粗梭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了级零?” 一聲冷哼從身側(cè)響起断医,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奏纪,沒(méi)想到半個(gè)月后鉴嗤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡序调,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年醉锅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片发绢。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硬耍,死狀恐怖垄琐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情默垄,我是刑警寧澤此虑,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站口锭,受9級(jí)特大地震影響朦前,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鹃操,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一韭寸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荆隘,春花似錦恩伺、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至燃观,卻和暖如春褒脯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缆毁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工番川, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脊框。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓颁督,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親浇雹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沉御,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)發(fā):LockForiOS 又到了春天挪坑的季節(jié),想起多次被問(wèn)及到鎖的概念昭灵,決定好好總結(jié)一番嚷节。 翻看目前關(guān)于 iO...
    Cooci_和諧學(xué)習(xí)_不急不躁閱讀 2,212評(píng)論 1 22
  • demo下載 建議一邊看文章,一邊看代碼虎锚。 聲明:關(guān)于性能的分析是基于我的測(cè)試代碼來(lái)的硫痰,我也看到和網(wǎng)上很多測(cè)試結(jié)果...
    炸街程序猿閱讀 789評(píng)論 0 2
  • Q:為什么出現(xiàn)多線程? A:為了實(shí)現(xiàn)同時(shí)干多件事的需求(并發(fā))窜护,同時(shí)進(jìn)行著下載和頁(yè)面UI刷新效斑。對(duì)于處理器,為每個(gè)線...
    幸福相依閱讀 1,576評(píng)論 0 2
  • 線程安全是怎么產(chǎn)生的 常見(jiàn)比如線程內(nèi)操作了一個(gè)線程外的非線程安全變量柱徙,這個(gè)時(shí)候一定要考慮線程安全和同步缓屠。 - (v...
    幽城88閱讀 656評(píng)論 0 0
  • 2018-11-26 事實(shí):一整天忙碌工作奇昙。 我的感受:煩,排斥敌完。 我的思維:不想去單位储耐,一踏進(jìn)辦公室就沒(méi)有了自己...
    晉春77閱讀 251評(píng)論 1 4