iOS @synchronized() 底層原理探索
作者:自如大前端研發(fā)中心-李長(zhǎng)鴻
https://juejin.cn/post/7017703842594684935
多個(gè)@synchronized() 嵌套,沒(méi)有意義也不會(huì)報(bào)錯(cuò);是objc中提供的同步鎖,支持遞歸。但是在swift中刪除了,可以使用objc_sync替代。
讀完本文你可以了解到synchronized的實(shí)現(xiàn)原理
我們今天重點(diǎn)討論一下下面幾個(gè)問(wèn)題
synchronized 的 obj 為 nil 會(huì)怎么樣 synchronized 會(huì)影響obj嗎 synchronized 和 pthread_mutex 以及objc_sync 的關(guān)系
我們驗(yàn)證一下嵌套
創(chuàng)建一個(gè)Person類(lèi),里面一個(gè)run方法
- (void)run {
@synchronized (self) {
NSLog(@"s1");
@synchronized (self) {
NSLog(@"s2");
}
}
}
執(zhí)行之后發(fā)現(xiàn)都是正常打印
swift和OC分別實(shí)現(xiàn)方式
///objc
@synchronized(self) {
//action
}
///swift
objc_sync_enter(self)
//action
objc_sync_exit(self)
生成runtime代碼查看底層實(shí)現(xiàn)
Person類(lèi)改為如下內(nèi)容
- (void)run {
@synchronized (self) {
NSLog(@"s1");
}
}
然后將Person轉(zhuǎn)為C++代碼 clang -x objective-c -rewrite-objc Person.m
打開(kāi)Person.cpp文件找到以下c++的代碼
static void _I_Person_run(Person * self, SEL _cmd) {
{ id _rethrow = 0; id _sync_obj = (id)self; 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_1m_kzn6dnx501b1x94s5bwpxjrw0000gn_T_Person_e7a30b_mi_0);
{ id _rethrow = 0; id _sync_obj = (id)self; 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_1m_kzn6dnx501b1x94s5bwpxjrw0000gn_T_Person_e7a30b_mi_1);
} 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);}
}
} 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);}
}
}
synchronized調(diào)用了try catch,內(nèi)部調(diào)用了objc_sync_enter和objc_sync_exit。這兩個(gè)函數(shù)又是如何實(shí)現(xiàn)的呢?
objc_sync_enter & objc_sync_exit
我們翻閱objc4源碼,在objc-sync.mm文件中,找到了具體實(shí)現(xiàn)
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
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;
}
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
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;
}
仔細(xì)看objc_sync_enter這段源碼和注釋?zhuān)芮宄枋隽诉@個(gè)函數(shù)的作用:1.在obj上開(kāi)始同步鎖 2.obj為nil,加鎖不會(huì)成功 3.obj不是nil,初始化遞歸互斥鎖(recursive mutex),并關(guān)聯(lián)obj
objc_sync_enter的加鎖方式
從底層源碼我們看到加鎖方式是先獲取obj關(guān)聯(lián)的同步數(shù)據(jù)SyncData,然后加鎖
SyncData同步數(shù)據(jù)是什么?
//objc-sync.mm
typedef struct SyncData {
//下一條同步數(shù)據(jù)
struct SyncData* nextData;
//鎖的對(duì)象
DisguisedPtr<objc_object> object;
//等待的線程數(shù)量
int32_t threadCount; // number of THREADS using this block
//互斥遞歸鎖
recursive_mutex_t mutex;
} SyncData;
SyncData是一個(gè)結(jié)構(gòu)體,類(lèi)似鏈表
nextData:SyncData的指針節(jié)點(diǎn),指向下一條數(shù)據(jù) object:鎖住的對(duì)象 threadCount:等待的線程數(shù)量 mutex:使用的遞歸互斥鎖
遞歸互斥鎖recursive_mutex_t具體實(shí)現(xiàn)
recursive_mutex_t是基于pthread_mutex_t的封裝。打開(kāi)objc-os.h找到具體實(shí)現(xiàn)
//objc-os.h
using recursive_mutex_t = recursive_mutex_tt<DEBUG>;
class recursive_mutex_tt : nocopy_t {
pthread_mutex_t mLock;
public:
recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) { }
void lock() {
lockdebug_recursive_mutex_lock(this);
int err = pthread_mutex_lock(&mLock);
if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err);
}
//這里省略......
}
synchronized的原理
內(nèi)部為每一個(gè)obj分配一把recursive_mutex遞歸互斥鎖。針對(duì)每個(gè)obj,通過(guò)這個(gè)recursive_mutex遞歸互斥鎖進(jìn)行加鎖、解鎖
內(nèi)部是如何管理obj和recursive_mutex的
這里就不一一深究了,有興趣的可以繼續(xù)看源碼
結(jié)論
synchronized 的 obj 為 nil 怎么辦?加鎖操作無(wú)效。 synchronized 會(huì)對(duì) obj 做什么操作嗎?會(huì)為obj生成遞歸自旋鎖,并建立關(guān)聯(lián),生成 SyncData,存儲(chǔ)在當(dāng)前線程的緩存里或者全局哈希表里。 synchronized 和 pthread_mutex 有什么關(guān)系?SyncData里的遞歸互斥鎖,使用 pthread_mutex 實(shí)現(xiàn)的。 synchronized 和 objc_sync 有什么關(guān)系?synchronized 底層調(diào)用了 objc_sync_enter() 和 objc_sync_exit()
作者:自如大前端研發(fā)中心-李長(zhǎng)鴻
https://juejin.cn/post/7017703842594684935
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來(lái),可以說(shuō)是程序員面試必備!所有資料都整理到網(wǎng)盤(pán)了,歡迎下載!

面試題】即可獲取