iOS @synchronized() 底層原理探索

語言: CN / TW / HK

多個@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; 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的原理

內部為每一個obj分配一把recursive_mutex遞歸互斥鎖。 針對每個obj,通過這個recursive_mutex遞歸互斥鎖進行加鎖、解鎖

內部是如何管理obj和recursive_mutex的

這裏就不一一深究了,有興趣的可以繼續看源碼

結論

  1. synchronized 的 obj 為 nil 怎麼辦? 加鎖操作無效。

  2. synchronized 會對 obj 做什麼操作嗎? 會為obj生成遞歸自旋鎖,並建立關聯,生成 SyncData,存儲在當前線程的緩存裏或者全局哈希表裏。

  3. synchronized 和 pthread_mutex 有什麼關係? SyncData裏的遞歸互斥鎖,使用 pthread_mutex 實現的。

  4. synchronized 和 objc_sync 有什麼關係? synchronized 底層調用了 objc_sync_enter() 和 objc_sync_exit()

本文作者:自如大前端研發中心-李長鴻