iOS @synchronized() 底層原理探索
多個@synchronized() 巢狀,沒有意義也不會報錯;是objc中提供的同步鎖,支援遞迴。但是在swift中刪除了,可以使用objc_sync替代。
讀完本文你可以瞭解到synchronized的實現原理
我們今天重點討論一下下面幾個問題 1. synchronized 的 obj 為 nil 會怎麼樣 2. synchronized 會影響obj嗎 3. synchronized 和 pthread_mutex 以及objc_sync 的關係
我們驗證一下巢狀
建立一個Person類,裡面一個run方法
- (void)run {
@synchronized (self) {
NSLog(@"s1");
@synchronized (self) {
NSLog(@"s2");
}
}
}
執行之後發現都是正常列印
swift和OC分別實現方式
``` ///objc @synchronized(self) { //action }
///swift objc_sync_enter(self) //action objc_sync_exit(self) ```
生成runtime程式碼檢視底層實現
Person類改為如下內容 ``` - (void)run { @synchronized (self) { NSLog(@"s1"); } }
``
然後將Person轉為C++程式碼
clang -x objective-c -rewrite-objc Person.m
`
開啟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呼叫了try catch,內部呼叫了objc_sync_enter和objc_sync_exit。這兩個函式又是如何實現的呢?
objc_sync_enter & objc_sync_exit
我們翻閱objc4原始碼,在objc-sync.mm檔案中,找到了具體實現
```
// 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;
} ```
仔細看objc_sync_enter這段原始碼和註釋,很清楚描述了這個函式的作用: 1.在obj上開始同步鎖 2.obj為nil,加鎖不會成功 3.obj不是nil,初始化遞迴互斥鎖(recursive mutex),並關聯obj
objc_sync_enter的加鎖方式
從底層原始碼我們看到加鎖方式是先獲取obj關聯的同步資料SyncData,然後加鎖
SyncData同步資料是什麼?
//objc-sync.mm
typedef struct SyncData {
//下一條同步資料
struct SyncData* nextData;
//鎖的物件
DisguisedPtr<objc_object> object;
//等待的執行緒數量
int32_t threadCount; // number of THREADS using this block
//互斥遞迴鎖
recursive_mutex_t mutex;
} SyncData;
SyncData是一個結構體,類似連結串列
nextData:SyncData的指標節點,指向下一條資料 object:鎖住的物件 threadCount:等待的執行緒數量 mutex:使用的遞迴互斥鎖
遞迴互斥鎖recursive_mutex_t具體實現
recursive_mutex_t是基於pthread_mutex_t的封裝。開啟objc-os.h找到具體實現
```
//objc-os.h
using recursive_mutex_t = recursive_mutex_tt
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的原理
內部為每一個obj分配一把recursive_mutex遞迴互斥鎖。 針對每個obj,通過這個recursive_mutex遞迴互斥鎖進行加鎖、解鎖
內部是如何管理obj和recursive_mutex的
這裡就不一一深究了,有興趣的可以繼續看原始碼
結論
-
synchronized 的 obj 為 nil 怎麼辦? 加鎖操作無效。
-
synchronized 會對 obj 做什麼操作嗎? 會為obj生成遞迴自旋鎖,並建立關聯,生成 SyncData,儲存在當前執行緒的快取裡或者全域性雜湊表裡。
-
synchronized 和 pthread_mutex 有什麼關係? SyncData裡的遞迴互斥鎖,使用 pthread_mutex 實現的。
-
synchronized 和 objc_sync 有什麼關係? synchronized 底層呼叫了 objc_sync_enter() 和 objc_sync_exit()
本文作者:自如大前端研發中心-李長鴻