iOS 多线程@synchronized原理

1,002 阅读2分钟

各种锁的性能分析

double_t beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < 100000; i++) {
             //加锁
            //解锁
         }
         double_t endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"时间间隔:%f:ms",(endTime - beginTime)*1000);

通过上面的方法对不同的锁进行性能测试得到如下结果:

截屏2021-08-22 上午10.52.25.png 发现@synchronized相对其他锁的效率比较低,但是相差不大

断点调试

截屏2021-08-22 上午11.03.16.png @synchronized调用的时候断点,然后通过汇编查看@synchronized底层调用了什么方法

截屏2021-08-22 上午11.03.33.png

objc_sync_enter

截屏2021-08-22 上午11.11.37.png 单看这个逻辑很简单,

  • 将传入的对象分装得到一个SyncData对象,对SyncData对象进行data->mutex.lock()加锁处理
  • 如果对象为nil,抛出提示,调用objc_sync_nil,不做任何处理

objc_sync_exit

截屏2021-08-22 上午11.17.57.png 这个实际上更简单

  • 取出SyncData对象,调用data->mutex.tryUnlock()解锁

SyncData结构分析

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;
  • nextData:又嵌套一个类型,由此可次看出可能是一个链表结构
  • object:对传入的对象做了一层包装
  • threadCount:调用锁里面任务的线程数量
  • recursive_mutex_t:递归锁

id2data,data的存储

TLS:线程局部存储(Thread Local Storage,TLS)用来将数据与一个正在执行的指定线程关联起来

源码分析

  • 第一步:StripedMap
SyncData **listp = &LIST_FOR_OBJ(object);
#define LIST_FOR_OBJ(obj) sDataLists[obj].data

static StripedMap<SyncList> sDataLists;

class StripedMap {

#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR

    enum { StripeCount = 8 };

#else

    enum { StripeCount = 64 };
    }

#endif

会有一个全局的hash标StripedMap链表来存储对象。其中他在真机的大小是8

  • 第二步:tls_get_direct 从当前线程的局部存储tls中,能否找到object对应的SyncData, 然后根据:ACQUIRE/RELEASE操作,对应的lockcount++/lockCount--; lockcount表示当前线程被锁的次数。
  • 第三步:fetch_cache 从缓存中快速获取object对应的SyncData,如果cache中存在就和第二步的操作一样。
  • 第四步:从链表里面找看有没有匹配的

截屏2021-08-22 下午4.40.41.png 这一步实际上从全局的链表里面找,看有没有匹配的,如果找到,goto done,创建缓存。

  • 第五步:创建一个全新的SyncData对象

截屏2021-08-22 下午4.51.30.png 创建一个全新的对象,利用头插法,插入到链表中

  • 第六步:创建cache缓存和TLS存储

截屏2021-08-22 下午4.53.56.png

拉链的产生

截屏2021-08-22 下午5.02.20.png 入上图所示,我们通过多线程使用@synchronized,为了出现hash冲突,修改StripeCount为1,然后调试源码,就会得到拉链的结果:

截屏2021-08-22 下午5.02.52.png

总结:@synchronized实际上就是在一般锁的基础上加了一些多线程的处理,这样多线程使用就更加安全。