iOS底层分析-锁(三)

这是我参与8月更文挑战的第25天,活动详情查看: 8月更文挑战

在上一篇文章iOS底层分析-锁(二)中我们介绍了sDataList的结构,接下来我们继续分析SyncData的存储逻辑及Synchronized的执行流程;

synchronized执行流程

第一次执行

我们给@synchronized打上断点,然后执行代码,看第一次代码如何执行:

我们发现,第一次执行@synchronized,代码直接执行此处代码;

根据注释,我们可以看到这段代码的逻辑:

  • 创建一个新的SyncData,并添加到listp中;
  • 第一次进来肯定有一个线程在操作,threadCount设为1
  • 初始化mutex锁;
  • result->nextData = *listp; *listp = result;说明在链表SyncList中采用头插法存放SyncData

继续执行:

将数据存储起来;然后返回SyncData类型的result;

第二次执行

继续执行,需要注意的是,我们第二次调用@synchronized的时候,传入的参数是p2,跟第一次传入的p1是不同的对象

代码执行到:

引入第一次执行的时候,我们已经将p1对应的SyncData存储起来了,所以此处data有值;但是我们继续执行之后,发现代码又来到了:

又重复了第一次的步骤,这是为什么呢?这是因为,我们传入的p2跟之前的p1不是同一个对象,所以存放的SyncList也不同,所以会重新生成新的SyncData;

那么我们将p2改为p1,然后来看一下代码第二次执行的逻辑:

因为前后两次传入的对象相同,所以进入判断;

  • 如果whyACQUIRElockCount1;
  • 如果whyRELEASElockCount1;
  • 如果lockCount0,说明当前SyncData当前线程解锁完成;OSAtomicDecrement32Barrier(&result->threadCount);result->threadCount进行递减操作;

但是其他线程可能还有加锁,说明了@synchronized具备可多线程;

lockCount说明了@synchronized具备可递归性;表示在同一个线程下边被锁的次数;
threadCount说明了@synchronized具备可多线程;表示被多少个线程加锁;

查找SyncData 方法解析

分析过SyncData的结构之后,我们继续分析在id2data方法内,SyncData是如何存储的?

  • SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);tls线程的局部存储(栈)区中查找是否存在SyncData
  • 如果存在SyncData,判断SyncDataobject和当前传入的object是否一致;如果不一致咋不做操作,继续向下执行;
  • 如果查询出的SyncDataobject和当前传入的object一致,那么先给result赋值data,然后获取lockCount,根据传入的why的值是ACQUIRE或者RELEASElockCount进行加++或者--的操作,将lockCount保存;最后返回result

实现流程与上面tls的流程非常相似;

总结

  • sDataLists是一张全局的哈希表,采用拉链法存放SyncData
  • sDataLists中的array存储的是SyncListSyncList绑定了object
  • @synchronized封装了objc_sync_enterobjc_sync_exit,其封装的是一个递归锁recursive_mutex_t;
  • SyncData支持两种存储原则:TLScache;
  • 第一次进入时,创建一个SyncData,采用头插法存储进链表结构;threadCount标记为1;
  • 第二次进入时,判断是不是同一个对象;如果不是同一个对象,需要重新创建SyncData,重新标记threadCount;
  • 如果是同一个对象,从TLS中找到SyncData之后进行lockCount++
  • 如果TLS中找不到SyncData,重新创建一个SyncData,并进行threadCount++
  • 如果whyRELEASE时进行lockCount--threadCount--

为什么synchronized具备可重入可递归多线程?

TLS保障threadCount标记对这个锁对象进行加锁的线程数量,lockCount++标记进来了多少次

synchronized的注意事项

  • 锁的对象不要为空;
  • 需要注意对象的生命周期;
  • 推荐使用self,除了生命周期的问题之外,传入同一个对象self时,我们只需要处理一条拉链;方便存储和释放;
  • 不要针对同一个对象,过多的加锁,导致拉链过长;
  • 真机和模拟器因为StripeCount不同的问题,导致性能相差巨大;
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享