將如下代碼clang查看一下
self.lockA = [[NSObject alloc] init];
@synchronized(self.lockA) {
NSLog(@"lockA");
};
得到如下結(jié)果:@Synchronized會變成一對基于try-catch的objc_sync_enter和objc_sync_exit的代碼
((void (*)(id, SEL, NSObject *))(void *)objc_msgSend)((id)self, sel_registerName("setLockA:"), ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
{ id _rethrow = 0; id _sync_obj = (id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("lockA")); objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
id sync_exit;
} _sync_exit(_sync_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_blmkswp957j3hbcpmjdtsvhw0000gn_T_TestSynchronized_ae1d39_mi_0);
} catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
};
既然如此钥屈,我么可以來看看objc_sync_enter內(nèi)部做了什么剃允。通過源碼我們可以找到:
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
result = recursive_mutex_lock(&data->mutex);
require_noerr_string(result, done, "mutex_lock failed");
} 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();
}
done:
return result;
可以看到锭吨,源碼中現(xiàn)將obj轉(zhuǎn)換成SyncData楞遏,然后開啟了SyncData的mutex鎖。并且@synchronized(nil)不起任何作用凰锡,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的原因盗棵。
? DisguisedPtr壮韭,這里DisguisedPtr其實就是對裸對象指針objc_object的一層包裝改寫。用于對象釋放后指向的指針纹因。
通過這邊文章http://satanwoo.github.io/2019/01/01/Synchronized/可知喷屋,id2data函數(shù)內(nèi)部實現(xiàn)會生成一個SyncList鏈表來管理SyncData,每一次都在多線程中執(zhí)行如下代碼:
- (void)test
{
@synchronized (self.testArray) {
self.testArray = @[].mutableCopy;
}
}
testArray每次都會被賦予不同的值瞭恰。例如當(dāng)前節(jié)點為SyncDataO袱巨,A線程現(xiàn)在生成新的SyncDataA節(jié)點(同時B線程在等待A修改完畢并解鎖)济赎,當(dāng)新的節(jié)點產(chǎn)生后筹燕,下一個B線程執(zhí)行SyncDataO節(jié)點上的處理(因為SyncDataA生成過程中栓票,該線程一直在等待,但是此時后者已經(jīng)獲取到了SyncDataO)颜启,那么當(dāng)再次進來一個新的線程C執(zhí)行新的更改時偷俭,獲取到的SyncDataC與SyncDataO不是同個節(jié)點,那么也就不是同一個鎖农曲,這時這兩者可以同時運行社搅。參考setter內(nèi)部代碼實現(xiàn):(代碼來自http://satanwoo.github.io/2019/01/01/Synchronized/)
static inline void reallySetProperty(id self, SEL _cmd, id newValue,
ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
id oldValue;
// 計算結(jié)構(gòu)體中的偏移量
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:NULL];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:NULL];
} else {
// 某些程度的優(yōu)化
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
// 危險區(qū)
if (!atomic) {
// 第一步
oldValue = *slot;
// 第二步
*slot = newValue;
} else {
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
oldValue = *slot;
*slot = newValue;
_spin_unlock(slotlock);
}
objc_release(oldValue);
}
可知,當(dāng)setter完成后乳规,我們會釋放oldValue形葬,但是
oldValue = *slot; id *slot = (id*) ((char*)self + offset);
也就是說*slot的值有可能都是SyncDataK的值,那么就有可能出現(xiàn)objc_release的可能是同一塊內(nèi)存暮的,也就會出現(xiàn)double free導(dǎo)致崩潰笙以。
總結(jié):對于@Synchronized(obj)的使用,盡量保證傳入的obj對象不會再鎖中被更改冻辩,同時多個不同的更改應(yīng)該建立不同的obj猖腕。例如
@Synchronized(lockA){
self.A = xxx;
};
@Synchronized(lockB){
self.B = xxx;
};
其本身的性能問題在目前來看拆祈,并不是我們應(yīng)該優(yōu)先考慮的點,相比直接用NSLock倘感,還是@Synchronized略快放坏。所以我們在使用過程中,盡量避免在@Synchronized代碼塊中進行耗時操作即可老玛,
文章參考:
http://www.cocoachina.com/ios/20161205/18279.html
http://satanwoo.github.io/2019/01/01/Synchronized/