美文网首页iOS学习
iOS-底层原理26-GCD补充和锁

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

作者: 一亩三分甜 | 来源:发表于2021-02-05 00:11 被阅读0次

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

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

    image.png
    image.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.png
    image.png
    image.png

    3.信号量

    image.png

    4.锁的性能

    • 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又存到栈存缓存中去

    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++,
    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.png

    2.多线程操作数据程序崩溃:_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

    相关文章

      网友评论

        本文标题:iOS-底层原理26-GCD补充和锁

        本文链接:https://www.haomeiwen.com/subject/drtwtltx.html