對(duì)于多線程你了解多少?對(duì)于鎖你又了解多少?鎖的原理你又知道嗎?
iOS底層探索之多線程(五)—GCD不同隊(duì)列源碼分析
iOS底層探索之多線程(六)—GCD源碼分析(sync 同步函數(shù)、async 異步函數(shù))
iOS底層探索之多線程(八)—GCD源碼分析(函數(shù)的同步性、異步性、單例)
iOS底層探索之多線程(九)—GCD源碼分析(柵欄函數(shù))
iOS底層探索之多線程(十)—GCD源碼分析( 信號(hào)量)
iOS底層探索之多線程(十一)—GCD源碼分析(調(diào)度組)
iOS底層探索之多線程(十四)—關(guān)于@synchronized鎖你了解多少?
1. 回顧
在上一篇博客中,已經(jīng)分析了第一次加鎖,data是空的,最后會(huì)創(chuàng)建SyncData并綁定到當(dāng)前線程上(一個(gè)線程只會(huì)綁定一個(gè),并且綁定后不再改變),注意此時(shí)并沒(méi)有保存到線程對(duì)應(yīng)的緩存列表中。
2. 源碼分析
單線程情況
那么現(xiàn)在去看看第二次加鎖,也就是斷點(diǎn)在44行時(shí),進(jìn)行跟蹤調(diào)試。
那么繼續(xù)
單步調(diào)式進(jìn)入源碼里面,斷點(diǎn)在id2data方法里面再進(jìn)行lldb的調(diào)式進(jìn)行分析。
從圖中控制臺(tái)
lldb的調(diào)試結(jié)果來(lái)看,第二次進(jìn)行加鎖時(shí),data里面是有數(shù)據(jù)的了。那么繼續(xù)過(guò)斷點(diǎn)看看,緩存里面的情況:此時(shí)緩存里面也有數(shù)據(jù)了,和上面打印的結(jié)果是一模一樣,都是
data = 0x0000000100837d40。然后會(huì)繼續(xù)判斷,傳入對(duì)象是否是和緩存里面的一樣。
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
如果是同一個(gè)對(duì)象,就會(huì)獲取lockCount,lockCount和threadCount是否小于等于 0進(jìn)行判斷,如果小于 0則會(huì)報(bào)錯(cuò)"id2data fastcache is buggy"
-
如果是
ACQUIRE則會(huì)lockCount++,再進(jìn)行鎖一次,說(shuō)明當(dāng)前對(duì)象鎖了兩次,如下:
lockCount++ -
如何是
RELEASE則會(huì)lockCount--,如果lockCount == 0,則會(huì)從線程空間緩存移除,這里也可以體現(xiàn)多線程的特性,從這句OSAtomicDecrement32Barrier(&result->threadCount)代碼可以看出,這是對(duì)線程進(jìn)行釋放。
RELEASE則會(huì)lockCount--
斷點(diǎn)繼續(xù),看看第三次加鎖的情況,如下:
因?yàn)槭菍?duì)同一個(gè)對(duì)象,進(jìn)行了重復(fù)的操作,加鎖了
3次,lockCount也是等于 3的,這也提現(xiàn)了拉鏈法,如下:因?yàn)槭峭粋€(gè)對(duì)象,每次加鎖,都會(huì)創(chuàng)建一個(gè)
SyncData,就一直往后拉著,通過(guò)一個(gè)鏈表來(lái)存。
以上都是對(duì)一個(gè)對(duì)象進(jìn)行重復(fù)的遞歸加鎖,那如果是不同對(duì)象呢?
不同對(duì)象也是類似的,就和上面那個(gè)結(jié)構(gòu)圖一樣,每個(gè)對(duì)象會(huì)創(chuàng)建一個(gè)拉鏈,同一個(gè)對(duì)象的就存在一個(gè)鏈表里面,這里就不再進(jìn)行舉例了,感興趣的老鐵可以自行測(cè)試,源碼戳這里
多線程遞歸情況
那么現(xiàn)在通過(guò)多線程加鎖會(huì)怎么樣呢?測(cè)試代碼如下:
斷點(diǎn)從
52 行開(kāi)始,進(jìn)入到源碼里面跟蹤調(diào)式,這時(shí)候進(jìn)入id2data方法,此時(shí)哈希表中的數(shù)據(jù)個(gè)數(shù)為2,也就是外層線程添加的兩個(gè)SyncData,如下圖:繼續(xù)跟蹤代碼,從線程中獲取其綁定的
SyncData,此時(shí)為NULL,因?yàn)槭切碌木€程,還沒(méi)有加過(guò)鎖,所以綁定數(shù)據(jù)為空,fastCacheOccupied=NO然后會(huì)繼續(xù)往下走,接著從緩存列表
fetch_cache中獲取對(duì)應(yīng)的·SyncData·,也是·NULL·,這里的緩存列表也是和線程一一對(duì)應(yīng)的起來(lái)的,都是空。繼續(xù)跟蹤流程,接著會(huì)進(jìn)行線程threadCount++操作,如下圖:
這里會(huì)從
listp中獲取對(duì)應(yīng)的數(shù)據(jù),在外層線程中,已經(jīng)添加了jp和jp2對(duì)應(yīng)的SyncData,這里是可以獲取到的,并且會(huì)對(duì)多線程操作,使得threadCount加1操作,此時(shí)對(duì)應(yīng)的線程數(shù)會(huì)從 1變成2,從上圖調(diào)試打印的結(jié)果可以很明顯的看到。
只要遇到新開(kāi)線程,開(kāi)始加鎖,tls和cache一定是空,肯定是listp中查找,或者是創(chuàng)建。一個(gè)線程中第一個(gè)添加的object一定會(huì)綁定到tls中,并且在當(dāng)前線程中不會(huì)改變。如果tls已經(jīng)完成設(shè)置,之后添加的SyncData都會(huì)添加到緩存列表中。
objc_sync_exit流程和這個(gè)相反,同樣會(huì)調(diào)用id2data方法,獲取SyncData,對(duì)lockCount和threadCount進(jìn)行減操作。如果count等于0,則會(huì)從相應(yīng)的綁定關(guān)系和緩存列表中移除。
使用@synchronized的注意事項(xiàng)
- 參數(shù)不傳
nil - 參數(shù)最好傳
self,方便存儲(chǔ)和釋放,如果是傳入一個(gè)這種JPStudent *jp = [[JPStudent alloc]init]的,這個(gè)jp是一個(gè)臨時(shí)的變量,如果有個(gè)多個(gè)這種,就會(huì)有多個(gè)拉鏈,耗費(fèi)內(nèi)存和性能,只使用一個(gè)self就只有一個(gè)拉鏈,雖然真機(jī)環(huán)境下,只有8個(gè),但是已經(jīng)夠用了,即便不夠用,系統(tǒng)也會(huì)及時(shí)釋放回收的。
不同平臺(tái)環(huán)境StripeCount數(shù)據(jù)個(gè)數(shù) - 在之前的博客中進(jìn)行的
@synchronized測(cè)試,為什么模擬器下性能比真機(jī)差呢?就是上圖中64的原因,模擬器拉鏈比較多,耗費(fèi)內(nèi)存和性能。
3. 總結(jié)
1: synchronized哈希表 - 拉鏈法 存儲(chǔ)SyncData
2: sDataLists里面是一個(gè) array存儲(chǔ)的是 SyncList,SyncList里面是綁定的object
3: objc_sync_enter / exit對(duì)稱 遞歸鎖
4: 兩種存儲(chǔ) : TLS/ Cache
5: 第?次的時(shí)候 SyncData 才用頭插法 -鏈表 ,標(biāo)記 thracount = 1
6: 然后下次再進(jìn)來(lái)會(huì)判斷是不是同?個(gè)對(duì)象
7: 是同一個(gè)對(duì)象TLS --> lockCount ++
8: 不是同一個(gè)的話TLS 找不到 就會(huì)去創(chuàng)建一個(gè)SyncData則threadCount ++
9: objc_sync_exit的話就是lockCount--和 threadCount--
@synchronized : 可重?遞歸 、多線程
1: 多線程是通過(guò)TLS保障threadCount 有多少條線程對(duì)這個(gè)鎖對(duì)象加鎖
2: 可重入遞歸是通過(guò)lockCount ++ 來(lái)表示進(jìn)來(lái)鎖了多少次
- 補(bǔ)充
TLS
線程局部存儲(chǔ)(Thread Local Storage,TLS): 是操作系統(tǒng)為線程單獨(dú)提供的私有空間,通常只有有限的容量。Linux系統(tǒng)下通常通過(guò)pthread庫(kù)中的
pthread_key_create()、
pthread_getspecific()、
pthread_setspecific()、
pthread_key_delete()等方法。
更多內(nèi)容持續(xù)更新
?? 喜歡就點(diǎn)個(gè)贊吧????
?? 覺(jué)得有收獲的,可以來(lái)一波,收藏+關(guān)注,評(píng)論 + 轉(zhuǎn)發(fā),以免你下次找不到我????
??歡迎大家留言交流,批評(píng)指正,互相學(xué)習(xí)??,提升自我??