深入詳解Objective-C中的@Synchronized關(guān)鍵字
正文
在多線(xiàn)程編程中,線(xiàn)程之間共享資源時(shí)容易出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題,導(dǎo)致程序出現(xiàn)不可預(yù)期的結(jié)果。為了避免這種情況,我們需要采用一些同步機(jī)制來(lái)保證線(xiàn)程之間的安全協(xié)作。 @synchronized指令是Objective-C中一種常用的同步機(jī)制。
@synchronized指令是Objective-C中一種非常簡(jiǎn)單方便的創(chuàng)建鎖的方式。相比于其他鎖,它的語(yǔ)法更加簡(jiǎn)單,只需要使用任意一個(gè)Objective-C對(duì)象作為鎖標(biāo)記即可。
- (void)myMethod:(id)anObj { @synchronized(anObj) { // Everything between the braces is protected by the @synchronized directive. } }
@synchronized指令中傳遞的對(duì)象是用于區(qū)分受保護(hù)代碼塊的唯一標(biāo)識(shí)符。如果在兩個(gè)不同的線(xiàn)程中執(zhí)行上述方法,分別為anObj參數(shù)傳遞不同的對(duì)象,那么每個(gè)線(xiàn)程都會(huì)獲取自己的鎖并繼續(xù)處理,而不會(huì)被另一個(gè)線(xiàn)程阻塞。但是,如果在這兩種情況下都傳遞相同的對(duì)象,則其中一個(gè)線(xiàn)程會(huì)首先獲取鎖,另一個(gè)線(xiàn)程則會(huì)被阻塞,直到第一個(gè)線(xiàn)程完成操作。
@Synchronized的底層實(shí)現(xiàn)
通過(guò)clang查看底層編譯代碼可知, @Synchronized是通過(guò)objc_sync_enter和objc_sync_exit函數(shù)來(lái)實(shí)現(xiàn)鎖的獲取和釋放的,源碼如下:
int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); ASSERT(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } return result; } int objc_sync_exit(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, RELEASE); if (!data) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } else { bool okay = data->mutex.tryUnlock(); if (!okay) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } } } else { // @synchronized(nil) does nothing } return result; }
- 如果傳入的obj存在,則走加鎖流程;如果obj為nil,則什么也不做。
- objc_sync_exit和objc_sync_enter是對(duì)應(yīng)的;objc_sync_exit方法就是解鎖,如果obj= nil則什么也不做;
通過(guò)觀(guān)察源碼可知,objc_sync_exit和objc_sync_enter里的關(guān)鍵是從obj轉(zhuǎn)換到SyncData,然后通過(guò)SyncData中的mutex來(lái)對(duì)臨界區(qū)上鎖。SyncData結(jié)構(gòu)體的定義如下:
typedef struct alignas(CacheLineSize) SyncData { struct SyncData* nextData; DisguisedPtr<objc_object> object; int32_t threadCount; // number of THREADS using this block recursive_mutex_t mutex; } SyncData;
- mutex是遞歸鎖,這也是為什么可以在 @Synchronized里嵌套 @Synchronized的原因了。
從obj轉(zhuǎn)換到SyncData的具體實(shí)現(xiàn)如下:
這段代碼實(shí)現(xiàn)了一個(gè)鎖的緩存機(jī)制,目的是為了提高多線(xiàn)程訪(fǎng)問(wèn)同一對(duì)象時(shí)的效率。當(dāng)多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)同一對(duì)象時(shí),每個(gè)線(xiàn)程需要獲取一個(gè)鎖,這會(huì)造成性能瓶頸。為了避免這個(gè)問(wèn)題,緩存機(jī)制會(huì)將已經(jīng)獲取的鎖緩存起來(lái),以供下次使用。其大致流程如下:
1、首先檢查是否啟用了快速緩存,如果啟用則在快速緩存中查找是否有與obj對(duì)應(yīng)的SyncData對(duì)象。
2、如果在快速緩存中找到了匹配的SyncData對(duì)象,則將syncLockCount加1,并返回結(jié)果。
3、如果沒(méi)有在快速緩存中找到匹配的SyncData對(duì)象,則繼續(xù)在線(xiàn)程緩存中查找是否有與obj對(duì)應(yīng)的鎖。
4、如果在線(xiàn)程緩存中找到了匹配的鎖,則將對(duì)應(yīng)鎖的計(jì)數(shù)加1,并將其返回結(jié)果。
5、如果沒(méi)有在線(xiàn)程緩存中找到匹配的鎖,則在全局的哈希表中查找是否有與obj對(duì)應(yīng)的SyncData對(duì)象。
6、如果在全局的哈希表中找到了匹配的SyncData對(duì)象,則會(huì)進(jìn)行多線(xiàn)程操作,將對(duì)應(yīng)鎖的計(jì)數(shù)加1,并返回結(jié)果。
7、如果沒(méi)有在全局的哈希表中找到匹配的SyncData對(duì)象,則創(chuàng)建新對(duì)象,并將新對(duì)象添加到上述的緩存中,以供下次使用。
badcase分析
#import "ViewController.h" @interface ViewController () @property (nonatomic, strong) NSMutableArray *testArray; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.testArray = @[].mutableCopy; for (NSUInteger i = 0; i < 5000; i++) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self testThreadArray]; }); } } - (void)testThreadArray { @synchronized (self.testArray) { self.testArray = @[].mutableCopy; } } @end
運(yùn)行這段代碼,會(huì)出現(xiàn)如下crash:
考慮這個(gè)場(chǎng)景,有三個(gè)線(xiàn)程A、B、C同時(shí)訪(fǎng)問(wèn)一個(gè)非原子屬性self.testArray,初始值為p0。線(xiàn)程A和線(xiàn)程B由于訪(fǎng)問(wèn)的self.testArray的值一致,產(chǎn)生了競(jìng)爭(zhēng),線(xiàn)程A獲取了鎖并將self.testArray的值重新設(shè)置為p1,然后釋放了鎖。此時(shí)線(xiàn)程C訪(fǎng)問(wèn)self.testArray,發(fā)現(xiàn)其值為p1,沒(méi)有競(jìng)爭(zhēng),準(zhǔn)備對(duì)其進(jìn)行賦值操作。然而,此時(shí)線(xiàn)程B由于之前的鎖已經(jīng)被釋放,進(jìn)入代碼塊,也準(zhǔn)備對(duì)self.testArray進(jìn)行賦值操作,這會(huì)導(dǎo)致兩個(gè)線(xiàn)程同時(shí)對(duì)非原子屬性self.testArray進(jìn)行賦值操作,從而產(chǎn)生crash。
以上就是深入詳解Objective-C中的@Synchronized關(guān)鍵字的詳細(xì)內(nèi)容,更多關(guān)于Objective-C @Synchronized關(guān)鍵字的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IOS開(kāi)發(fā)網(wǎng)絡(luò)篇—Socket編程詳解
這篇文章主要介紹了IOS開(kāi)發(fā)網(wǎng)絡(luò)篇—Socket編程的相關(guān)資料,需要的朋友可以參考下2016-09-09iOS開(kāi)發(fā)WebViewJavascriptBridge通訊原理解析
這篇文章主要為大家介紹了iOS開(kāi)發(fā)WebViewJavascriptBridge通訊原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11iOS中利用UIBezierPath + CAAnimation實(shí)現(xiàn)心跳動(dòng)畫(huà)效果
這篇文章主要給大家介紹了關(guān)于iOS中利用UIBezierPath + CAAnimation實(shí)現(xiàn)心跳動(dòng)畫(huà)效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的日常開(kāi)發(fā)具有一定的參考學(xué)習(xí),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10iOS NSThread和NSOperation的基本使用詳解
下面小編就為大家分享一篇iOS NSThread和NSOperation的基本使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01iOS中管理剪切板的UIPasteboard粘貼板類(lèi)用法詳解
在iOS中,通過(guò)UITextField、UITextView和UIWebView剪切或復(fù)制的內(nèi)容都可以通過(guò)UIPasteboard類(lèi)來(lái)管理粘貼操作,下面就為大家?guī)?lái)iOS中管理剪切板的UIPasteboard粘貼板類(lèi)用法詳解:2016-06-06iOS中定位當(dāng)前位置坐標(biāo)及轉(zhuǎn)換為火星坐標(biāo)的方法
這篇文章主要介紹了iOS中獲取當(dāng)前位置坐標(biāo)及轉(zhuǎn)換為火星坐標(biāo)的方法,這里的火星坐標(biāo)指的是我國(guó)專(zhuān)門(mén)研制的一種加密的坐標(biāo)系統(tǒng)...需要的朋友可以參考下2016-02-02使用IOS AirPrint實(shí)現(xiàn)打印功能詳解
這篇文章主要介紹了使用IOS AirPrint實(shí)現(xiàn)打印功能詳解,想了解無(wú)線(xiàn)打印的同學(xué),一定要看一下2021-04-04