首页 > 应用开发 > IT综合 > 进行婚恋相亲系统开发,需要了解的底层结构
锁对我们而言不陌生但是又很陌生,当多个线程操作同一个资源的时候为了内存安全,婚恋相亲系统开发需要对资源进行保护,那么我们需要使用锁。常用的锁如
@synchronized、
NSRecursiveLock、
NSLock、以及属性的原子操作锁
atomic等等,他们有什么区别呢?我们该如何理解并正确使用呢?
看一下下面的例子
输出:
分析:如上图所示,如果婚恋相亲系统开发不加锁,输出当前余票就会发生错乱,我们想看到的应该是顺序递减的,那么在上面
saleTicket方法中加一个
@synchronized锁就可以解决这种多线程导致的数据不安全的问题。
那么再看下面的例子
输出:
分析:testMethod是一个处理业务的代码块,里面进行了递归调用,如果使用NSLock锁就会产生死锁,因为理论上输出日志应该是从10开始递减,那么换成
@synchronized如何呢?
输出:
分析:看到这我们至少知道@synchronized可以解决NSLock因为递归导致的死锁问题,说明NSLock是一把非递归锁,如果把NSLock换成
递归锁NSRecursiveLock也能解决这个问题,那么NSRecursiveLock能解决
多线程的问题吗?
结果:在婚恋相亲系统开发多线程的情况下NSRecursiveLock递归锁会导致程序奔溃,而换成@synchronized同样可以解决在多线程下的递归问题。看样子
@synchronized确实有点屌,不仅能多线程加锁而且可以递归调用,下面就分析下源码看看它是如何实现的。
随便写一个@synchronized通过汇编看看底层调用了什么符号,然后再跟进源码看细节,也可以通过clang编译一下源文件看看@synchronized编译成了什么。
通过汇编发现@synchronized底层是调用了
objc_sync_enter加锁和
objc_sync_exit解锁,我们进objc源码看下这两个函数。
分析:objc_sync_enter、objc_sync_exit逻辑上是一样的,一个是加锁一个是解锁。首先判断obj是否为空,如果不为空就构造一个SyncData对象并且加锁,如果为空就什么也不操作,所以在使用
@synchronized(obj)时必须要传递一个对象否则加不了锁。加锁操作是通过对象SyncData获取的
mutex.lock(),看下
SyncData结构以及
id2data方法是如何生成的SyncData。
分析:通过
SyncData对象我们稍微有了一点点明悟,
threadCount应该就是@synchronized可以多线程加锁的原因,
recursive_mutex_t应该就是@synchronized可以递归的原因,至于
nextData我们目前只知道是一个链表结构,接下来看下创建这个对象的函数id2data()
分析:创建SyncData流程如下
哈希表SyncList中通过object对象的哈希下标获取listp,注意是通过对象的哈希下标寻址,在存储的时候如果对象的
哈希下标一样,就通过
拉链法存储。tls(
线程局部存储)中获取object对应的syncData,如果获取到了,就把该对象在tls中
lockcount++或者
lockcount--,加还是减根据是加锁还是解锁判断。
每个对象都绑定
一个syncData,每个线程的tls都不一样,如果是同一个对象单线程递归加锁,就会进入该流程。线程缓存中获取object对应的syncData,如果获取到了,就把该对象在线程缓存中lockcount++或者lockcount--,加还是减根据是加锁还是解锁判断。tls内存容量很小,一般同一个线程第一个对象会存在tls中,其他对象都会存在线程缓存中。tls和线程缓存都找不到该对象,说明是不同线程或不同对象又或者是第一次加锁,如果listp不为空,就循环
遍历listp的拉链表,直到找到与object对应的syncData,使它
threadcount++,简单点说就是
多线程对同一个对象加锁时会进入该流程。tls和线程缓存都找不到该对象并且非多线程,根据object创建syncData,并且指定syncData中属性
nextData为上一个syncData,这是
头插法,也就是每次新来的数据会插在之前数据的前边哈希表
SyncList源码如下:
static sDataLists是一个
全局的哈希表,真机情况的存储
SyncList个数是
8个,其它环境
64个,SyncList是一个封装着SyncData的结构体,使用拉链法来存储 SyncData,同一个线程不同对象加锁时很大概率会触发拉链存储,
拉链存储的前提是对象的
哈希值一样,用下面这张图辅助理解下这个哈希表的结构
为了方便断点调试演示拉链,把源码中StripedMap里StripeCount改为2
增加hash冲突的概率。
断点打在第一个@synchronized
对象
lgter1加锁,此时
*listp值为空,创建syncData对象result并且把
result的nextData指向
*listp即为空对象。过了
241行后,*listp被赋值成了对象result,此时哈希表被更新有值了,继续往下走会把对象lgter1对应的
syncData存进
tls
断点打在第二个@synchronized
对象
lgter2加锁时,此时
*listp不会空,说明lgter2和lgter1
哈希下标是一样的,
listp是根据下标在哈希表中取值的,继续往下走不会进tls,因为虽然是同一个线程但不是同一个对象,也不会进线程缓存中查找,最后会创建一个新的syncData对象result并且把result的nextData指向listp即为上一个对象lgter1的syncData,这样就形成了
拉链。
婚恋相亲系统开发继续往下走对象会被存进
线程缓存而不是
tls,tls只有线程第一次给对象加锁时才会被存入。
声明:本文由云豹科技转发自顶风尿一丈博客,如有侵权请联系作者删除
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70002045/viewspace-2839542/ ,如需转载,请注明出处,否则将追究法律责任。