阅读 222

GCD-死锁-单例-栅栏-信号量-调度组

死锁

  • 写一个串行队列的异步线程任务,再加一个同步线程任务,发生死锁报错

- (void)textDemo1{          dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);     NSLog(@"1");     dispatch_async(queue, ^{         NSLog(@"2");         dispatch_sync(queue, ^{             NSLog(@"3");         });         NSLog(@"4");     });     NSLog(@"5"); } 结果是 1、5、2正常执行完成后发生死锁 分析 1.串行队列任务是先进先出 2.dispatch_async 块任务中加入了 2 dispatch_sync 块任务 和 4 ,其中 dispatch_sync中要执行3,队列中任务顺序是 1、5、2、 同步块、 4 3 。 3.2完成后执行dispatch_sync任务,同步函数需要执行3才能执行4,但是4在3任务之前加入的,依据先进先出的原则,只有执行完4才能执行3,任务之间互相等待产生死锁问题。 复制代码

  • 报错堆栈信息

image.png

  • 在函数dispatch_sync_f_slow函数之后

image.png

  • 找到报错函数

image.png

image.png

在队列上调用dispatch_sync "已被当前线程拥有"

  • 死锁的条件 dq_state,dsc->dsc_waiter 相同时,需要调用的线程是一个等待的线程,产生死锁。

if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) { DISPATCH_CLIENT_CRASH((uintptr_t)dq_state, "dispatch_sync called on queue " "already owned by current thread"); } 复制代码

单例原理分析

  • val参数为全局静态变量,block参数为任务封装,l是这个全局静态变量的门属性

image.png

  • dispatch_once_f

image.png

  • 线程锁,线程安全

image.png

  • return _dispatch_once_callout(l, ctxt, func); 执行block中的任务

  • 完成任务之后进行关门处理

image.png

  • 对两个宏的赋值处理

image.png

  • 标记位done

image.png

  • return _dispatch_once_wait(l);如果当前状态还没有完成,同时也没有标记位done,则进入等待

栅栏函数

最直接的作用:控制任务执行顺序,同步

  • dispatch_barrier_async 前面的任务执行完毕才会到这里

  • dispatch_barrier_sync 作用相同,但是这个会阻塞线程,影响后面的任务执行

  • 栅栏函数只能控制同一并发队列,全部并发队列不允许。

  • 底层分析

image.png

  • _dispatch_barrier_sync_f

image.png

  • _dispatch_barrier_sync_f_inline

image.png 进入_dispatch_sync_f_slow或_dispatch_sync_recurse

  • _dispatch_sync_recurse

image.png 这里有一个do-while的死循环递归只有当前队列的任务全部清空完成后才能走下一步 _dispatch_sync_invoke_and_complete_recurse

  • _dispatch_sync_invoke_and_complete_recurse

image.png

  • _dispatch_sync_complete_recurse

image.png do-while 判断是否存在barrier,存在则dx_wakeup把前面的任务都唤醒执行,完成之后进入_dispatch_lane_non_barrier_complete表示当前已经完成了并且没有了barrier

  • _dispatch_lane_non_barrier_complete

image.png 进行状态修复,否则一直dx_wakeup死循环

  • dx_wakeup 就是dq_wakeup,

image.png

  • 会根据不同的队列类型赋值不同的函数,进行不同的函数调用, image.png

    • 全局并发队列则调用_dispatch_root_queue_wakeup

    • 普通并发队列则调用_dispatch_lane_wakeup

    • 查看两个函数的不同类解释为什么全局并发队列不能执行栅栏函数。

  • _dispatch_root_queue_wakeup

image.png

  • _dispatch_lane_wakeup image.png image.png image.png 做一个do-while循环保证上面的任务完成后进入_dispatch_lane_non_barrier_complete_finish移除队列中的栅栏

    • 如果是同步队列 dq_width = 1 则开始等待

    • 如果是异步队列进入_dispatch_lane_drain_non_barriers

    • 判断当前队列中是否有barrier是则进入_dispatch_lane_barrier_complete栅栏完成函数,

  • 最后进入_dispatch_lane_class_barrier_complete 完成栅栏函数中的任务。

image.png 清空队列中的barrier标记,按正常流程完成任务。 由于全局并发队列中还有系统任务后台任务的函数需要处理,所以加栅栏堵塞,后台任务就无法执行,影响了这个系统的处理。

  • 栅栏的缺点,由于业务网络请求一般都是使用的AFN网络请求,队列的创建在框架中完成,我们无法获取网络请求的队列,所以栅栏函数使用会比较麻烦。

信号量dispatch_semaphore_t

  • dispatch_semaphore_create 创建信号量

  • dispatch_semaphore_wait 信号等待

  • dispatch_semaphore_signal 信号量释放

  • 同步->当锁,控制GCD最大并发数,可以控制一次完成的任务数量

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);     dispatch_semaphore_t sem = dispatch_semaphore_create(1);     dispatch_queue_t queue1 = dispatch_queue_create("cooci", NULL);     //任务1       dispatch_async(queue, ^{         dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待         NSLog(@"执行任务1");         NSLog(@"任务1完成");         dispatch_semaphore_signal(sem); // 发信号     });          //任务2     dispatch_async(queue, ^{         dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待         sleep(2);         NSLog(@"执行任务2");         NSLog(@"任务2完成");         dispatch_semaphore_signal(sem); // 发信号     });          //任务3     dispatch_async(queue, ^{         dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);         sleep(2);         NSLog(@"执行任务3");         NSLog(@"任务3完成");         dispatch_semaphore_signal(sem);     });     //任务4     dispatch_async(queue, ^{         dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);         sleep(2);         NSLog(@"执行任务4");         NSLog(@"任务4完成");         dispatch_semaphore_signal(sem);     }); 复制代码

分析

  • 信号量创建只要大于或等于0都是有用的否则没用

image.png

  • dispatch_semaphore_wait

image.png os_atomic_dec2o = --1 value>=0则重置为0, 当我创建的信号量=0时,则进入_dispatch_semaphore_wait_slow函数

  • _dispatch_semaphore_wait_slow

image.png 进入一个switch判断时长,如果是错误的不符合规则,则跳出去, DISPATCH_TIME_NOW,则超时处理,DISPATCH_TIME_FOREVER``则进入_dispatch_sema4_wait

  • _dispatch_sema4_wait

image.png 是一个do-while循环的等待

  • dispatch_semaphore_signal

image.png os_atomic_inc2o = ++1,value>0则重置为0,正常执行,如果加1后还是小于0,报出异常,dispatch_semaphore_wait操作过多,则进入_dispatch_semaphore_signal_slow

  • _dispatch_semaphore_signal_slow

image.png 信号量创建+1,

结论:信号量的原理就是value值++和--,当值小于0进入do-while循环等待这个任务就进入等待,等待信号量变为正,当另外一个任务完成后,++之后信号量大于0,则进入下一个任务。

调度组

作用:控制任务执行顺序 dispatch_group_create 创建组 dispatch_group_async 进组任务 dispatch_group_notify 进组任务执行完毕通知 dispatch_group_wait 进组任务执行等待事件 dispatch_group_enter 进组 dispatch_group_leave 出组

  • 调度组的简单使用

- (void)viewDidLoad {     [super viewDidLoad];     self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 300, 300, 200)];     self.imageView.image = [UIImage imageNamed:@"backImage"];     [self.view addSubview:self.imageView];          [self groupDemo]; } /**  调度组测试  */ - (void)groupDemo{      //    dispatch_group_enter(group); //    dispatch_group_leave(group);          dispatch_group_t group = dispatch_group_create();     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);     dispatch_group_async(group, queue, ^{         //创建调度组         NSString *logoStr1 = @"https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09f14cef6f3d4859a85610da67ed38de~tplv-k3u1fbpfcp-watermark.image?";         NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];         UIImage *image1 = [UIImage imageWithData:data1];         [self.mArray addObject:image1];     }); //    dispatch_group_async(group, queue, ^{ //        //创建调度组 //       NSString *logoStr2 = @"https://f12.baidu.com/it/u=3172787957,1000491180&fm=72"; //        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]]; //        UIImage *image2 = [UIImage imageWithData:data2]; //        [self.mArray addObject:image2]; //    });      //      进组和出租 成对  先进后出 //    dispatch_group_leave(group);     dispatch_group_enter(group);     dispatch_async(queue, ^{         //创建调度组        NSString *logoStr2 = @"https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09f14cef6f3d4859a85610da67ed38de~tplv-k3u1fbpfcp-watermark.image?";         NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];         UIImage *image2 = [UIImage imageWithData:data2];         [self.mArray addObject:image2]; //        dispatch_group_enter(group);         dispatch_group_leave(group);     });      //    long time = dispatch_group_wait(group, 1); // //    if (time == 0) { // //    }               dispatch_group_notify(group, dispatch_get_main_queue(), ^{         UIImage *newImage = nil;        NSLog(@"数组个数:%ld",self.mArray.count);        for (int i = 0; i<self.mArray.count; i++) {            UIImage *waterImage = self.mArray[i];            newImage =[KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];        }         self.imageView.image = newImage;     }); } 复制代码

  • 实现图片的水印效果

image.png

  • 问题1:调度组是如何控制流程的

  • 问题2:调度组进组和出组的搭配奔溃问题

  • 问题3:dispatch_group_async = dispatch_group_enter+dispatch_group_leave

底层分析

  • dispatch_group_create

image.png

image.png

  • dispatch_group_enter(dispatch_group_t dg)

image.png 这里传入的dg传入的是0这里的一个--操作信号量变为-1

  • dispatch_group_leave

image.png 这里的操作是将-1变为0,os_atomic_add_orig2o这个是+1的操作, image.png

  • 1.加入dg=-1,old_state = -1 + 1 = 0,old_value与操作后就是0

  • 2.加入dg=0, old_state = 1 + 1 = 1,old_value与操作后就是1

image.png

  • 操作后=0时,进入判断后显然0不等于DISPATCH_GROUP_VALUE_1,do-while循环处理状态操作后,进入_dispatch_group_wake唤醒dispatch_group_notify操作,否则1的时候就是进入下面的判断,报错处理

  • _dispatch_group_notify

image.png 判断old_state==0才会_dispatch_group_wake唤醒 整个流程可以简略为0 - 1后阻塞,+1后唤醒下一步操作

  • dispatch_group_async

image.png

  • _dispatch_continuation_group_async

image.png 这里有一个进组的操作将原始的信号量0变成-1

  • 流程分析

image.png

image.png

  • 假设现在是全局并发队列一下流程是执行block的流程分析

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

  • 如果这里是有group的标签

image.png

  • _dispatch_continuation_with_group_invoke

image.png 这里执行了离开组的操作,

  • 执行block

image.png 总结:_dispatch_continuation_group_async其实内部自动执行了进组与出组的操作

在日常的开过程中,我们经常会用到NSTimerNSTimer需要加入到NSRunloop中,还受到mode的影响。在mode设置不对的情况下,scrollView滑动的时候NSTimer也会收到影响。如果Runloop正在进行连续性的运行,timer就可能会被延迟

GCD提供了一个解决方案dispatch_source源。dispatch_source有以下几种特性:

  • 时间较准确,CPU负荷小,占用资源少

  • 可以使用子线程,解决定时器跑在主线程上卡UI问题

  • 可以暂停,继续,不用像NSTimer一样需要重新创建

dispatch_source源的关键方法:

  • dispatch_source_create 创建源

  • dispatch_source_set_event_handler 设置源事件回调

  • dispatch_source_merge_data 源事件设置数据

  • dispatch_source_get_data 获取源事件数据

  • dispatch_resume 继续

  • dispatch_suspend 挂起

两个重要的参数:

  • dispatch_source_type_t 要创建的源类型

  • dispatch_queue_t 事件处理程序块将提交到的调度队列

事件源类型:

  • DISPATCH_SOURCE_TYPE_DATA_ADD 用于合并数据

  • DISPATCH_SOURCE_TYPE_DATA_OR 按位OR用于合并数据

  • DISPATCH_SOURCE_TYPE_DATA_REPLACE 新获得的数据值替换现有的

  • DISPATCH_SOURCE_TYPE_MACH_SEND 监视Mach端口的调度源,只有发送权,没有接收权
    -DISPATCH_SOURCE_TYPE_MACH_RECV 监视Mach端口的待处理消息

  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 监控系统的变化,内存压力状况

  • DISPATCH_SOURCE_TYPE_PROC 监视外部进程的事件的调度源

  • DISPATCH_SOURCE_TYPE_READ 监控文件描述符的调度源可供读取的字节

  • DISPATCH_SOURCE_TYPE_SIGNAL 用于监视当前进程的信号

  • DISPATCH_SOURCE_TYPE_TIMER 基于计时器的调度源

  • DISPATCH_SOURCE_TYPE_VNODE 监视事件文件描述符的调度源

  • DISPATCH_SOURCE_TYPE_WRITE 监视事件,写入字节的缓冲区空间

 伪原创工具 SEO网站优化  https://www.237it.com/ 

作者:Fade_VV
链接:https://juejin.cn/post/7035631626411311118

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐