死锁
- 写一个串行队列的异步线程任务,再加一个同步线程任务,发生死锁报错
- (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,任务之间互相等待产生死锁问题。
复制代码
- 报错堆栈信息
- 在函数dispatch_sync_f_slow函数之后
- 找到报错函数
在队列上调用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是这个全局静态变量的门属性
- dispatch_once_f
- 线程锁,线程安全
- return _dispatch_once_callout(l, ctxt, func); 执行block中的任务
- 完成任务之后进行关门处理
- 对两个宏的赋值处理
- 标记位done
- return _dispatch_once_wait(l);如果当前状态还没有完成,同时也没有标记位done,则进入等待
栅栏函数
最直接的作用:控制任务执行顺序,同步
-
dispatch_barrier_async 前面的任务执行完毕才会到这里
-
dispatch_barrier_sync 作用相同,但是这个会阻塞线程,影响后面的任务执行
-
栅栏函数只能控制同一并发队列,全部并发队列不允许。
-
底层分析
- _dispatch_barrier_sync_f
- _dispatch_barrier_sync_f_inline
进入_dispatch_sync_f_slow或_dispatch_sync_recurse
- _dispatch_sync_recurse
这里有一个do-while的死循环递归只有当前队列的任务全部清空完成后才能走下一步 _dispatch_sync_invoke_and_complete_recurse
- _dispatch_sync_invoke_and_complete_recurse
- _dispatch_sync_complete_recurse
do-while 判断是否存在barrier,存在则dx_wakeup
把前面的任务都唤醒执行,完成之后进入_dispatch_lane_non_barrier_complete
表示当前已经完成了并且没有了barrier
- _dispatch_lane_non_barrier_complete
进行状态修复,否则一直dx_wakeup死循环
- dx_wakeup 就是dq_wakeup,
- 会根据不同的队列类型赋值不同的函数,进行不同的函数调用,
- 全局并发队列则调用_dispatch_root_queue_wakeup
- 普通并发队列则调用_dispatch_lane_wakeup
- 查看两个函数的不同类解释为什么全局并发队列不能执行栅栏函数。
- _dispatch_root_queue_wakeup
- _dispatch_lane_wakeup
- 判断当前队列中是否有barrier是则进入
_dispatch_lane_barrier_complete
栅栏完成函数,
- 如果是同步队列 dq_width = 1 则开始等待
- 如果是异步队列进入_dispatch_lane_drain_non_barriers
_dispatch_lane_non_barrier_complete_finish
移除队列中的栅栏 - 判断当前队列中是否有barrier是则进入
- 最后进入_dispatch_lane_class_barrier_complete 完成栅栏函数中的任务。
清空队列中的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都是有用的否则没用
- dispatch_semaphore_wait
os_atomic_dec2o
= --1 value>=0则重置为0, 当我创建的信号量=0时,则进入_dispatch_semaphore_wait_slow
函数
- _dispatch_semaphore_wait_slow
进入一个switch判断时长,如果是错误的不符合规则,则跳出去, DISPATCH_TIME_NOW
,则超时处理,DISPATCH_TIME_FOREVER``则进入_dispatch_sema4_wait
- _dispatch_sema4_wait
是一个do-while循环的等待
- dispatch_semaphore_signal
os_atomic_inc2o
= ++1,value>0则重置为0,正常执行,如果加1后还是小于0,报出异常,dispatch_semaphore_wait
操作过多,则进入_dispatch_semaphore_signal_slow
- _dispatch_semaphore_signal_slow
信号量创建+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;
});
}
复制代码
- 实现图片的水印效果
- 问题1:调度组是如何控制流程的
- 问题2:调度组进组和出组的搭配奔溃问题
- 问题3:dispatch_group_async = dispatch_group_enter+dispatch_group_leave
底层分析
- dispatch_group_create
- dispatch_group_enter(dispatch_group_t dg)
这里传入的dg传入的是0这里的一个--操作信号量变为-1
- dispatch_group_leave
这里的操作是将-1变为0,os_atomic_add_orig2o
这个是+1的操作,
- 1.加入dg=-1,old_state = -1 + 1 = 0,old_value与操作后就是0
- 2.加入dg=0, old_state = 1 + 1 = 1,old_value与操作后就是1
-
操作后=0时,进入判断后显然0不等于DISPATCH_GROUP_VALUE_1,do-while循环处理状态操作后,进入
_dispatch_group_wake
唤醒dispatch_group_notify
操作,否则1的时候就是进入下面的判断,报错处理 -
_dispatch_group_notify
判断old_state==0才会_dispatch_group_wake
唤醒 整个流程可以简略为0 - 1后阻塞,+1后唤醒下一步操作
- dispatch_group_async
- _dispatch_continuation_group_async
这里有一个进组的操作将原始的信号量0变成-1
- 流程分析
- 假设现在是全局并发队列一下流程是执行block的流程分析
- 如果这里是有group的标签
- _dispatch_continuation_with_group_invoke
这里执行了离开组的操作,
- 执行block
总结:_dispatch_continuation_group_async其实内部自动执行了进组与出组的操作
在日常的开过程中,我们经常会用到NSTimer
。NSTimer
需要加入到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
监视事件,写入字节的缓冲区空间