iOS-底层原理26-GCD补充和锁
《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理25-GCD(下)》介绍了GCD单例,栅栏函数,同步函数,信号量底层原理,本文接着介绍GCD调度组合dispatch_source以及锁
1.调度组:dispatch_group_enter和dispatch_group_leave要成对出现,如下载两张图片完成后更新UI,两个异步线程任务会进入同一个组,只有等两个线程任务都leave之后才会进行通知
image.pngimage.png
若将通知移到前面dispatch_group_notify并不需要保证两个线程组任务都完成,只需保证enter和leave是成对出现的就OK,不管远近
image.png
若多写一个dispatch_group_enter,则会回不到dispatch_group_notify中
image.png
若多写一个dispatch_group_leave,则会崩溃,奔溃的位置dispatch_group_leave(group)会最后执行,因为里面的任务有延迟sleep(1)
image.png
也可以将dispatch_group_leave(group)写到touchBegan中,控制dispatch_group_notify,点击屏幕,触发dispatch_group_leave(self.group)通知dispatch_group_notify回到主线程更新UI
image.png
dispatch_group_async和(dispatch_group_enter,dispatch_group_leave)起到相同的效果,执行完后触发dispatch_group_notify
image.png
dispatch_group_enter,dispatch_group_leave,dispatch_group_notify,dispatch_group_async底层原理呢?dispatch_group_enter,dispatch_group_leave为什么等价于dispatch_group_async?dispatch_group_async里面是否包含dispatch_group_enter和dispatch_group_leave
image.png
2.dispatch_source:有很多种类型如DISPATCH_SOURCE_TYPE_DATA_ADD,DISPATCH_SOURCE_TYPE_DATA_REPLACE,DISPATCH_SOURCE_TYPE_TIMER等并不依赖与runloop,和下层内核workLoop进行交互,调用时DISPATCH_SOURCE_TYPE_TIMER会很准确
image.pngimage.png
image.png
3.信号量
image.png4.锁的性能
-
1.在全局并发队列里面买票,若不能保证原子性同步状态,会发生票絮乱,加上同步锁后,保证数据的原子性
image.png -
2.@synchronized(self)底层原理,先符号断点查看汇编信息发现有两个标记位一个是objc_sync_enter,一个是objc_sync_exit
image.png
image.png -
3.clang查看@synchronized(self)源码,objc_sync_enter(_sync_obj)和objc_sync_exit(_sync_obj)分别在哪个源码里面呢?继续看汇编发现源码在libobjc.A.dylib
image.png
image.png -
4.递归锁是一种带有递归性质的互斥锁,进入objc_sync_enter和objc_sync_exit源码分析
image.png -
I.obj为nil的情况,objc_sync_nil(),下符号断点objc_sync_nil,传入的obj为nil,则什么都没做
image.png
image.png
image.png
image.png
image.png -
II.objc_sync_enter中obj不为nil的情况SyncData* data = id2data(obj,ACQUIRE)进行data->mutex.lock(),在objc_sync_enter中obj不为nil的情况下进行data->mutex.tryUnlock()解锁,加锁与解锁成对存在
image.png
image.png
- 递归互斥锁:recursive_mutex_t
- 嵌套 - 可重入 - 递归
lockCount
enter:lockCount++,通过lockCount判断任务代码块被锁了多少次,间接验证可嵌套(可重入),可重复被锁
image.png
objc_sync_enter : lock
objc_sync_enter : unlock
static SyncData* id2data(id object, enum usage why)底层原理分析
image.png
objc_sync_enter和objc_sync_exit会分别调用SyncData* data = id2data(obj, ACQUIRE)和SyncData* data = id2data(obj, RELEASE)传入ACQUIRE、RELEASE对应lockCount++、lockCount--进行代码复用,代码进出都会调用
image.png
可以对代码块进行多线程重复锁,若要从内存中获取一把锁,应该设计成什么结构,方便于查找和断开,设计成哈希表的拉链形式
image.png
image.png
若要获取哪个线程的哪个SyncData,需先找到下标,第一次进来后发现result有值,后面就会将*listP赋值给result->nextData
image.png
image.png
若没有线程栈存缓存SyncData的形式,则通过缓存的形式SyncCache获取
image.png
分三种情况
1.第一次进来,没有锁:两种缓存都没有,进入代码块,result->threadCount=1,进入goto done代码,result,lockCount=1存到tls里面,方便下次查找,全局栈存缓存里面也存一次
image.png
![image.png](https://img.haomeiwen.com/i4193251/f5f6150b7c126aff.png?imageMogr2/auto-orient/
strip%7CimageView2/2/w/1240)
2.不是第一次,同一个线程:第二次进入在栈存缓存中能找到,lockCount++,1+1又存到栈存缓存中去
3.不是第一次,不同线程:第三次栈存缓存(新的线程3)中没有,线程3,对全局的线程空间进行查找,threadCount++和lockCount++,存到全局线程栈存cache中,之前第一步进行存储时传入的是cache = fetch_cache(YES),存储到全局栈存缓存总表中,第一次进入新建全局栈存缓存空间cache总表进行存储,宏定义_objc_pthread_key唯一,
#define _objc_pthread_key TLS_DIRECT_KEY # define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
存储到分配的内存空间cache总表中唯一,第二次进行查找是传入NO不会再重新分配栈存缓存空间,能直接查找到,查找到之后进行获取item,SyncCacheItem *item = &cache->list[i]
进行ACQUIRE,lockCount++,从全局栈存缓存空间中获取到cache总表进行查找遍历item,判断和当前的self是否是同一个对象,若相同,则取出第一次存的item,进行lockCount++,image.png
image.png
image.png
image.png
image.png
此时并不会return result,而是跳入下面的代码块,对threadCount进行++操作进入done函数
image.png
lockCount的值每次存进去的时候都为1,代表总表里面的线程分表的锁住次数始终为1,每个线程的表在总表中都做一次初始化,这个值不会再变了,后面再次进来的时候,就走单个的线程缓存了不会再走总表的线程缓存了,这儿的lockCount为1和前面的lockCount++代表的不是同一个概念,前面lockCount++表示同一个线程锁住block的次数,这儿的lockCount就是总表中的子线程表的初始化lockCount为1,做个标记
image.png
封装了一把锁recursive_mutex_t,为什么可以重入重复锁,增加了lockCount,防止多线程重入,增加了threadCount
同理objc_sync_exit进行反向操作,进行release
5.@synchronize坑点:@synchronized(self) : 性能 + objc 生命周期,enter 找节点 objc : 查找非常麻烦 - self
1.性能低,链表的查询,缓存下层的代码不断的查找,锁self的节点过多,链表查询变慢
image.png2.多线程操作数据程序崩溃:_testArray = [NSMutableArray array]
相当于进行setter操作,对新值retain,对旧值进行release,某一瞬间同时两个线程对_testArray指向的区域进行release操作,但只进行了一次retain操作,retainCount只为1,不够减,形成野指针,造成崩溃
image.png
image.png
image.png
查看是否为野指针,通过添加Zombie Objects,解决办法是加锁,可以加@synchronized,若用_testArray为锁中的对象,则不能,_testArray还有可能为空,锁self是可以的,为什么呢?self持有testArray,若self释放了为nil,则testArray必定释放了,self伴随了testArray整个生命周期
image.png
image.png
image.png
image.png
还可以用NSLock进行加锁,还有其他锁的类型且看下篇文章分析
image.png