iOS @synchronized() 底层原理探索

iOS开发

共 10285字,需浏览 21分钟

 ·

2021-10-24 08:45

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

作者:自如大前端研发中心-李长鸿

https://juejin.cn/post/7017703842594684935

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

内部为每一个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()

作者:自如大前端研发中心-李长鸿

https://juejin.cn/post/7017703842594684935


-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击👆卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报