iOS底層探索之多線程(十五)—@synchronized源碼分析

對(duì)于多線程你了解多少?對(duì)于鎖你又了解多少?鎖的原理你又知道嗎?

@synchronized

iOS底層探索之多線程(一)—進(jìn)程和線程

iOS底層探索之多線程(二)—線程和鎖

iOS底層探索之多線程(三)—初識(shí)GCD

iOS底層探索之多線程(四)—GCD的隊(duì)列

iOS底層探索之多線程(五)—GCD不同隊(duì)列源碼分析

iOS底層探索之多線程(六)—GCD源碼分析(sync 同步函數(shù)、async 異步函數(shù))

iOS底層探索之多線程(七)—GCD源碼分析(死鎖的原因)

iOS底層探索之多線程(八)—GCD源碼分析(函數(shù)的同步性、異步性、單例)

iOS底層探索之多線程(九)—GCD源碼分析(柵欄函數(shù))

iOS底層探索之多線程(十)—GCD源碼分析( 信號(hào)量)

iOS底層探索之多線程(十一)—GCD源碼分析(調(diào)度組)

iOS底層探索之多線程(十二)—GCD源碼分析(事件源)

iOS底層探索之多線程(十三)—鎖的種類你知多少?

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)試。

第二次加鎖調(diào)試

那么繼續(xù)單步調(diào)式進(jìn)入源碼里面,斷點(diǎn)在id2data方法里面再進(jìn)行lldb的調(diào)式進(jìn)行分析。

打印列表數(shù)據(jù)

從圖中控制臺(tái) lldb的調(diào)試結(jié)果來(lái)看,第二次進(jìn)行加鎖時(shí),data里面是有數(shù)據(jù)的了。那么繼續(xù)過(guò)斷點(diǎn)看看,緩存里面的情況:
tls_get_direct

此時(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,lockCountthreadCount是否小于等于 0進(jìn)行判斷,如果小于 0則會(huì)報(bào)錯(cuò)"id2data fastcache is buggy"

對(duì)lockCount進(jìn)行操作

  • 如果是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)了拉鏈法,如下:
SyncData哈希鏈表結(jié)構(gòu)

因?yàn)槭峭粋€(gè)對(duì)象,每次加鎖,都會(huì)創(chuàng)建一個(gè)SyncData,就一直往后拉著,通過(guò)一個(gè)鏈表來(lái)存。

以上都是對(duì)一個(gè)對(duì)象進(jìn)行重復(fù)的遞歸加鎖,那如果是不同對(duì)象呢?

不同對(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,如下圖:
調(diào)試結(jié)果

繼續(xù)跟蹤代碼,從線程中獲取其綁定的SyncData,此時(shí)為NULL,因?yàn)槭切碌木€程,還沒(méi)有加過(guò)鎖,所以綁定數(shù)據(jù)為空,fastCacheOccupied=NO
調(diào)試結(jié)果

然后會(huì)繼續(xù)往下走,接著從緩存列表fetch_cache中獲取對(duì)應(yīng)的·SyncData·,也是·NULL·,這里的緩存列表也是和線程一一對(duì)應(yīng)的起來(lái)的,都是空。
fetch_cache也是空的

繼續(xù)跟蹤流程,接著會(huì)進(jìn)行線程threadCount++操作,如下圖:

調(diào)試結(jié)果

這里會(huì)從listp中獲取對(duì)應(yīng)的數(shù)據(jù),在外層線程中,已經(jīng)添加了jpjp2對(duì)應(yīng)的SyncData,這里是可以獲取到的,并且會(huì)對(duì)多線程操作,使得threadCount1操作,此時(shí)對(duì)應(yīng)的線程數(shù)會(huì)從 1變成2,從上圖調(diào)試打印的結(jié)果可以很明顯的看到。

只要遇到新開(kāi)線程,開(kāi)始加鎖,tlscache一定是空,肯定是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ì)lockCountthreadCount進(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è)SyncDatathreadCount ++
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í)??,提升自我??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容