iOS中鎖的分析
** @synchronized ** 遞歸互斥鎖
// objc_sync_enter lock 加鎖
// objc_sync_exit 解鎖
recursive_mutex_t 遞歸鎖
1卖哎、當(dāng)如果鎖的是nil送膳,那么久直接retrun返回
2私植、SyncData 進(jìn)行了兩種方案,一種是從棧里面保存這個(gè)鎖,一種是用cache里面保存這把鎖
objc_sync_enter & objc_sync_exit 分析
進(jìn)入objc_sync_enter源碼實(shí)現(xiàn)
如果obj存在邑时,則通過(guò)id2data方法獲取相應(yīng)的SyncData,對(duì)threadCount、lockCount進(jìn)行遞增操作
-
如果obj不存在客给,則調(diào)用objc_sync_nil,通過(guò)符號(hào)斷點(diǎn)得知肢簿,這個(gè)方法里面什么都沒(méi)做靶剑,直接return了
image.jpeg
data->mutex.lock();//加鎖
進(jìn)入objc_sync_exit源碼實(shí)現(xiàn)
如果obj存在,則調(diào)用id2data方法獲取對(duì)應(yīng)的SyncData池充,對(duì)threadCount桩引、lockCount進(jìn)行遞減操作
如果obj為nil,什么也不做
-
image.jpeg
進(jìn)入SyncData的定義收夸,是一個(gè)結(jié)構(gòu)體坑匠,主要用來(lái)表示一個(gè)線程data,類似于鏈表結(jié)構(gòu)卧惜,有next指向厘灼,且封裝了recursive_mutex_t屬性,可以確認(rèn)@synchronized確實(shí)是一個(gè)遞歸互斥鎖
【第一步】首先在tls即線程緩存中查找咽瓷。
- 在tls_get_direct方法中以線程為key设凹,通過(guò)KVC的方式獲取與之綁定的SyncData,即線程data忱详。其中的tls()围来,表示本地局部的線程緩存,
- 判斷獲取的data是否存在匈睁,以及判斷data中是否能找到對(duì)應(yīng)的object
- 如果都找到了监透,在tls_get_direct方法中以KVC的方式獲取lockCount,用來(lái)記錄對(duì)象被鎖了幾次(即鎖的嵌套次數(shù))
- 如果data中的threadCount 小于等于0航唆,或者 lockCount 小于等于0時(shí)胀蛮,則直接崩潰
- 通過(guò)傳入的why,判斷是操作類型
- 如果是ACQUIRE糯钙,表示加鎖粪狼,則進(jìn)行l(wèi)ockCount++,并保存到tls緩存
- 如果是RELEASE任岸,表示釋放再榄,則進(jìn)行l(wèi)ockCount--,并保存到tls緩存享潜。如果lockCount 等于 0困鸥,從tls中移除線程data
- 如果是CHECK,則什么也不做
** 【第二步】如果tls中沒(méi)有剑按,則在cache緩存中查找*
通過(guò)fetch_cache方法查找cache緩存中是否有線程
如果有疾就,則遍歷cache總表澜术,讀取出線程對(duì)應(yīng)的SyncCacheItem
-
從SyncCacheItem中取出data,然后后續(xù)步驟與tls的匹配是一致的
【第三步】如果cache中也沒(méi)有猬腰,即第一次進(jìn)來(lái)鸟废,則創(chuàng)建SyncData,并存儲(chǔ)到相應(yīng)緩存中
如果在cache中找到線程姑荷,且與object相等盒延,則進(jìn)行賦值、以及threadCount++
如果在cache中沒(méi)有找到厢拭,則threadCount等于1
所以在id2data方法中兰英,主要分為三種情況
【第一次進(jìn)來(lái),沒(méi)有鎖】:
- threadCount = 1
- lockCount = 1
- 存儲(chǔ)到tls 【不是第一次進(jìn)來(lái)供鸠,且是同一個(gè)線程】
- tls中有數(shù)據(jù)畦贸,則lockCount++
- 存儲(chǔ)到tls 【不是第一次進(jìn)來(lái),且是不同線程】
- 全局線程空間進(jìn)行查找線程
- threadCount++
- lockCount++
- 存儲(chǔ)到cache 總結(jié)
- @synchronized在底層封裝的是一把遞歸鎖楞捂,所以這個(gè)鎖是遞歸互斥鎖薄坏,底層通過(guò)TLS緩存和cache緩存
- @synchronized的可重入,即可嵌套寨闹,主要是由于lockCount 和 threadCount的搭配
- @synchronized使用鏈表的原因是鏈表方便下一個(gè)data的插入胶坠,
- 但是由于底層中鏈表查詢、緩存的查找以及遞歸繁堡,是非常耗內(nèi)存以及性能的沈善,導(dǎo)致性能低,所以在前文中椭蹄,該鎖的排名在最后
- 但是目前該鎖的使用頻率仍然很高闻牡,主要是因?yàn)榉奖愫?jiǎn)單,且不用解鎖
- 不能使用非OC對(duì)象作為加鎖對(duì)象绳矩,因?yàn)槠鋙bject的參數(shù)為id
- @synchronized (self)這種適用于嵌套次數(shù)較少的場(chǎng)景罩润。這里鎖住的對(duì)象也并不永遠(yuǎn)是self,這里需要讀者注意
- 如果鎖嵌套次數(shù)較多翼馆,即鎖self過(guò)多割以,會(huì)導(dǎo)致底層的查找非常麻煩,因?yàn)槠涞讓邮擎湵磉M(jìn)行查找应媚,所以會(huì)相對(duì)比較麻煩严沥,所以此時(shí)可以使用NSLock、信號(hào)量等
NSLock
NSLock是對(duì)下層pthread_mutex的封裝中姜,使用如下
請(qǐng)問(wèn)下面block嵌套block的代碼中消玄,會(huì)有什么問(wèn)題?
會(huì)出現(xiàn)一直等待的情況,主要是因?yàn)榍短资褂玫倪f歸莱找,使用NSLock(簡(jiǎn)單的互斥鎖,如果沒(méi)有回來(lái)嗜桌,會(huì)一直睡覺(jué)等待)奥溺,即會(huì)存在一直加lock,等不到unlock 的堵塞情況
pthread_mutex就是互斥鎖本身骨宠,當(dāng)鎖被占用浮定,其他線程申請(qǐng)鎖時(shí),不會(huì)一直忙等待层亿,而是阻塞線程并睡眠
NSRecursiveLock
NSRecursiveLock在底層也是對(duì)pthread_mutex的封裝
對(duì)比NSLock 和 NSRecursiveLock桦卒,其底層實(shí)現(xiàn)幾乎一模一樣,區(qū)別在于init時(shí)匿又,NSRecursiveLock有一個(gè)標(biāo)識(shí)PTHREAD_MUTEX_RECURSIVE方灾,而NSLock是默認(rèn)的
NSCondition
NSCondition 是一個(gè)條件鎖,在日常開(kāi)發(fā)中使用較少碌更,與信號(hào)量有點(diǎn)相似:線程1需要滿足條件1才會(huì)往下走裕偿,否則會(huì)堵塞等待,知道條件滿足痛单。經(jīng)典模型是生產(chǎn)消費(fèi)者模型
NSCondition的對(duì)象實(shí)際上作為一個(gè)鎖 和 一個(gè)線程檢查器
鎖主要 為了當(dāng)檢測(cè)條件時(shí)保護(hù)數(shù)據(jù)源嘿棘,執(zhí)行條件引發(fā)的任務(wù)
-
線程檢查器主要是根據(jù)條件決定是否繼續(xù)運(yùn)行線程,即線程是否被阻塞
image.jpeg其底層也是對(duì)下層pthread_mutex的封裝
NSCondition是對(duì)mutex和cond的一種封裝(cond就是用于訪問(wèn)和操作特定類型數(shù)據(jù)的指針)
wait操作會(huì)阻塞線程旭绒,使其進(jìn)入休眠狀態(tài)鸟妙,直至超時(shí)
signal操作是喚醒一個(gè)正在休眠等待的線程
broadcast會(huì)喚醒所有正在等待的線程
NSConditionLock
NSConditionLock是條件鎖,一旦一個(gè)線程獲得鎖挥吵,其他線程一定等待
相比NSConditionLock而言重父,NSCondition使用比較麻煩,所以推薦使用NSConditionLock蔫劣,其使用如下
NSConditionLock坪郭,其本質(zhì)就是NSCondition + Lock
線程 1 調(diào)用[NSConditionLock lockWhenCondition:],此時(shí)此刻因?yàn)椴粷M足當(dāng)前條件脉幢,所以會(huì)進(jìn)入 waiting 狀態(tài)歪沃,當(dāng)前進(jìn)入到 waiting 時(shí),會(huì)釋放當(dāng)前的互斥鎖嫌松。
此時(shí)當(dāng)前的線程 3 調(diào)用[NSConditionLock lock:]沪曙,本質(zhì)上是調(diào)用 [NSConditionLock lockBeforeDate:],這里不需要比對(duì)條件值萎羔,所以線程 3 會(huì)打印
接下來(lái)線程 2 執(zhí)行[NSConditionLock lockWhenCondition:]液走,因?yàn)闈M足條件值,所以線程2 會(huì)打印,打印完成后會(huì)調(diào)用[NSConditionLock unlockWithCondition:],這個(gè)時(shí)候?qū)alue 設(shè)置為 1缘眶,并發(fā)送 boradcast, 此時(shí)線程 1 接收到當(dāng)前的信號(hào)嘱根,喚醒執(zhí)行并打印。
自此當(dāng)前打印為 線程 3->線程 2 -> 線程 1
[NSConditionLock lockWhenCondition:];這里會(huì)根據(jù)傳入的 condition 值和 Value 值進(jìn)行對(duì)比巷懈,如果不相等该抒,這里就會(huì)阻塞,進(jìn)入線程池顶燕,否則的話就繼續(xù)代碼執(zhí)行[NSConditionLock unlockWithCondition:]: 這里會(huì)先更改當(dāng)前的 value 值凑保,然后進(jìn)行廣播,喚醒當(dāng)前的線程