iOS-底层原理26-GCD补充和锁

《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理25-GCD(下)》介绍了GCD单例,栅栏函数,同步函数,信号量底层原理,本文接着介绍GCD调度组合dispatch_source以及锁

1.调度组:dispatch_group_enter和dispatch_group_leave要成对出现,如下载两张图片完成后更新UI,两个异步线程任务会进入同一个组,只有等两个线程任务都leave之后才会进行通知

iOS-底层原理26-GCD补充和锁_第1张图片
image.png

iOS-底层原理26-GCD补充和锁_第2张图片
image.png

若将通知移到前面dispatch_group_notify并不需要保证两个线程组任务都完成,只需保证enter和leave是成对出现的就OK,不管远近


iOS-底层原理26-GCD补充和锁_第3张图片
image.png

若多写一个dispatch_group_enter,则会回不到dispatch_group_notify中
iOS-底层原理26-GCD补充和锁_第4张图片
image.png

若多写一个dispatch_group_leave,则会崩溃,奔溃的位置dispatch_group_leave(group)会最后执行,因为里面的任务有延迟sleep(1)


iOS-底层原理26-GCD补充和锁_第5张图片
image.png

也可以将dispatch_group_leave(group)写到touchBegan中,控制dispatch_group_notify,点击屏幕,触发dispatch_group_leave(self.group)通知dispatch_group_notify回到主线程更新UI
iOS-底层原理26-GCD补充和锁_第6张图片
image.png

dispatch_group_async和(dispatch_group_enter,dispatch_group_leave)起到相同的效果,执行完后触发dispatch_group_notify
iOS-底层原理26-GCD补充和锁_第7张图片
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
iOS-底层原理26-GCD补充和锁_第8张图片
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会很准确

iOS-底层原理26-GCD补充和锁_第9张图片
image.png

iOS-底层原理26-GCD补充和锁_第10张图片
image.png

iOS-底层原理26-GCD补充和锁_第11张图片
image.png

3.信号量

iOS-底层原理26-GCD补充和锁_第12张图片
image.png

4.锁的性能

  • 1.在全局并发队列里面买票,若不能保证原子性同步状态,会发生票絮乱,加上同步锁后,保证数据的原子性


    iOS-底层原理26-GCD补充和锁_第13张图片
    image.png
  • 2.@synchronized(self)底层原理,先符号断点查看汇编信息发现有两个标记位一个是objc_sync_enter,一个是objc_sync_exit


    iOS-底层原理26-GCD补充和锁_第14张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第15张图片
    image.png
  • 3.clang查看@synchronized(self)源码,objc_sync_enter(_sync_obj)和objc_sync_exit(_sync_obj)分别在哪个源码里面呢?继续看汇编发现源码在libobjc.A.dylib


    iOS-底层原理26-GCD补充和锁_第16张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第17张图片
    image.png
  • 4.递归锁是一种带有递归性质的互斥锁,进入objc_sync_enter和objc_sync_exit源码分析


    iOS-底层原理26-GCD补充和锁_第18张图片
    image.png
  • I.obj为nil的情况,objc_sync_nil(),下符号断点objc_sync_nil,传入的obj为nil,则什么都没做


    iOS-底层原理26-GCD补充和锁_第19张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第20张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第21张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第22张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第23张图片
    image.png
  • II.objc_sync_enter中obj不为nil的情况SyncData* data = id2data(obj,ACQUIRE)进行data->mutex.lock(),在objc_sync_enter中obj不为nil的情况下进行data->mutex.tryUnlock()解锁,加锁与解锁成对存在


    iOS-底层原理26-GCD补充和锁_第24张图片
    image.png

    iOS-底层原理26-GCD补充和锁_第25张图片
    image.png
  • 递归互斥锁:recursive_mutex_t
  • 嵌套 - 可重入 - 递归
  • lockCount
    enter:lockCount++,通过lockCount判断任务代码块被锁了多少次,间接验证可嵌套(可重入),可重复被锁


    iOS-底层原理26-GCD补充和锁_第26张图片
    image.png
objc_sync_enter : lock
objc_sync_enter : unlock

static SyncData* id2data(id object, enum usage why)底层原理分析


iOS-底层原理26-GCD补充和锁_第27张图片
image.png

objc_sync_enter和objc_sync_exit会分别调用SyncData* data = id2data(obj, ACQUIRE)和SyncData* data = id2data(obj, RELEASE)传入ACQUIRE、RELEASE对应lockCount++、lockCount--进行代码复用,代码进出都会调用


iOS-底层原理26-GCD补充和锁_第28张图片
image.png

可以对代码块进行多线程重复锁,若要从内存中获取一把锁,应该设计成什么结构,方便于查找和断开,设计成哈希表的拉链形式
iOS-底层原理26-GCD补充和锁_第29张图片
image.png

iOS-底层原理26-GCD补充和锁_第30张图片
image.png

若要获取哪个线程的哪个SyncData,需先找到下标,第一次进来后发现result有值,后面就会将*listP赋值给result->nextData


iOS-底层原理26-GCD补充和锁_第31张图片
image.png

iOS-底层原理26-GCD补充和锁_第32张图片
image.png

若没有线程栈存缓存SyncData的形式,则通过缓存的形式SyncCache获取
image.png

分三种情况
1.第一次进来,没有锁:两种缓存都没有,进入代码块,result->threadCount=1,进入goto done代码,result,lockCount=1存到tls里面,方便下次查找,全局栈存缓存里面也存一次
iOS-底层原理26-GCD补充和锁_第33张图片
image.png
iOS-底层原理26-GCD补充和锁_第34张图片
image.png

2.不是第一次,同一个线程:第二次进入在栈存缓存中能找到,lockCount++,1+1又存到栈存缓存中去

iOS-底层原理26-GCD补充和锁_第35张图片
image.png

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++,
iOS-底层原理26-GCD补充和锁_第36张图片
image.png

iOS-底层原理26-GCD补充和锁_第37张图片
image.png

iOS-底层原理26-GCD补充和锁_第38张图片
image.png

iOS-底层原理26-GCD补充和锁_第39张图片
image.png

iOS-底层原理26-GCD补充和锁_第40张图片
image.png

此时并不会return result,而是跳入下面的代码块,对threadCount进行++操作进入done函数
iOS-底层原理26-GCD补充和锁_第41张图片
image.png

lockCount的值每次存进去的时候都为1,代表总表里面的线程分表的锁住次数始终为1,每个线程的表在总表中都做一次初始化,这个值不会再变了,后面再次进来的时候,就走单个的线程缓存了不会再走总表的线程缓存了,这儿的lockCount为1和前面的lockCount++代表的不是同一个概念,前面lockCount++表示同一个线程锁住block的次数,这儿的lockCount就是总表中的子线程表的初始化lockCount为1,做个标记
iOS-底层原理26-GCD补充和锁_第42张图片
image.png

封装了一把锁recursive_mutex_t,为什么可以重入重复锁,增加了lockCount,防止多线程重入,增加了threadCount
同理objc_sync_exit进行反向操作,进行release

5.@synchronize坑点:@synchronized(self) : 性能 + objc 生命周期,enter 找节点 objc : 查找非常麻烦 - self

1.性能低,链表的查询,缓存下层的代码不断的查找,锁self的节点过多,链表查询变慢

iOS-底层原理26-GCD补充和锁_第43张图片
image.png

2.多线程操作数据程序崩溃:_testArray = [NSMutableArray array]相当于进行setter操作,对新值retain,对旧值进行release,某一瞬间同时两个线程对_testArray指向的区域进行release操作,但只进行了一次retain操作,retainCount只为1,不够减,形成野指针,造成崩溃

image.png

image.png

iOS-底层原理26-GCD补充和锁_第44张图片
image.png

查看是否为野指针,通过添加Zombie Objects,解决办法是加锁,可以加@synchronized,若用_testArray为锁中的对象,则不能,_testArray还有可能为空,锁self是可以的,为什么呢?self持有testArray,若self释放了为nil,则testArray必定释放了,self伴随了testArray整个生命周期


iOS-底层原理26-GCD补充和锁_第45张图片
image.png

iOS-底层原理26-GCD补充和锁_第46张图片
image.png

image.png

iOS-底层原理26-GCD补充和锁_第47张图片
image.png

还可以用NSLock进行加锁,还有其他锁的类型且看下篇文章分析


iOS-底层原理26-GCD补充和锁_第48张图片
image.png

你可能感兴趣的:(iOS-底层原理26-GCD补充和锁)