Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 最高优先级
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级在所有高优先级队列都被安排之后,但是在任何低优先级队列被调度之前。
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 发送到队列的将以低优先级运行,即,队列最终将被安排执行默认优先级和高先级队列已经存在。计划。
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 分配给队列的项将以后台优先级(即队列)运行将在所有优先级较高的队列被调度后进行执行(这是最低的优先级)
iOS8.0之后的权限
QOS_CLASS_USER_INTERACTIVE 与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
QOS_CLASS_USER_INITIATED 由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
QOS_CLASS_DEFAULT 优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
QOS_CLASS_UTILITY 一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
QOS_CLASS_BACKGROUND 这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
QOS_CLASS_UNSPECIFIED 未指定
创建一个队列在不同版本有不同的选择
// 如果iOS 8和以上版本的话,创建queue的方法和之前的版本的不太太一样。在iOS 8和以上的版本中创建queue需要先创建一个dispatch_queue_attr_t类型的实例。并作为参数传入到queue的生成方法里。
dispatch_queue_t queue;
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queue = dispatch_queue_create("com.tao.render", attr);
} else {
queue = dispatch_queue_create("com.tao.render", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
}
一、串行队列
Serial Diapatch Queue 串行队列
当任务相互依赖,具有明显的先后顺序的时候,使用串行队列是一个不错的选择
创建一个串行队列:
- (void)dispatchSerialqueueTest {
///第一个参数为队列名,第二个参数为队列类型,当然,第二个参数人如果写NULL,创建出来的也是一个串行队列。然后我们在异步线程来执行这个队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.tao.serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"serial 1");
sleep(1);
});
dispatch_async(serialQueue, ^{
sleep(2);
NSLog(@"serial 2");
});
dispatch_async(serialQueue, ^{
sleep(2);
NSLog(@"serial 3");
});
}
二、并发队列
Concurrent Diapatch Queue 并发队列
与串行队列刚好相反,他不会存在任务间的相互依赖。
- (void)dispatchConcurrentQueueTest {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
sleep(2);
NSLog(@"2");
});
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"3");
});
}
三、系统队列
Global Queue & Main Queue
这是系统为我们准备的2个队列:
Global Queue 其实就是系统创建的Concurrent Diapatch Queue (并发)
Main Queue 其实就是系统创建的位于主线程的Serial Diapatch Queue (串行)
下面也测试了之前说的一些队列优先级
- (void)dispatchGlobalAndMainQueue {
/***
异步主线程
在日常工作中,除了在其他线程返回主线程的时候需要用这个方法,还有一些时候我们在主线程中直接调用异步主线程,这是利用dispatch_async的特性:block中的任务会放在主线程本次runloop之后返回。这样,有些存在先后顺序的问题就可以得到解决了。
**/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"异步线程");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"异步主线程");
});
});
/**** dispatch_get_global_queue存在优先级,没错,他一共有4个优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
**/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(@"4");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(@"3");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"1");
});
}
四、队列优先级的操控
dispatch_set_target_queue 操作队列优先级方法
我把自己创建的队列塞到了系统提供的global_queue队列中,我们可以理解为:我们自己创建的queue其实是位于global_queue中执行,所以改变global_queue的优先级,也就改变了我们自己所创建的queue的优先级。所以我们常用这种方式来管理子队列。
根据(先进先出原则 FIFO)
- (void)dispatchSetTargetTest {
dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(dispatchgetglobalqueue,serialDiapatchQueue);///先进先出原则,先调用后面的,在调用前面的
dispatch_async(serialDiapatchQueue, ^{
NSLog(@"我优先级低,先让让");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"我优先级高,我先block");
});
}
五、延迟调用方法 定时器
单次执行定时器
dispatch_after
这个是最常用的,用来延迟执行的GCD方法,因为在主线程中我们不能用sleep来延迟方法的调用,所以用它是最合适的,
我们看到他就是在主线程,就是刚好延迟了2秒,当然,我说这个2秒并不是绝对的,为什么这么说?dispatch_async的block中的方法执行会放在主线程runloop之后,所以,如果此时runloop周期较长的时候,可能会有一些时差产生。
多次执行定时器
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);// 建立一个定时器对象
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0);// 设置定时器相关参数
dispatch_source_set_event_handler(_timer, ^{ //定时器执行事件}
使用注意:需要把_timer 申请为全局变量,不然的话,会导致,_timer被释放,从而导致定时器不触发
- (void)dispatchAfterTest {
// 单次定时器
NSLog(@"单次定时器前");
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"单次定时器后");
});
// 多次定时器使用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 设置默认优先级
NSTimeInterval period = 1.0; //设置时间间隔
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //第一个参数 dispatch_source_t 对象 第二个参数 延迟多少秒开始执行 第三个参数 每秒执行 最后一个参数,允许误差多少秒
dispatch_source_set_event_handler(_timer, ^{ //在这里执行事件
timeInt ++;
NSLog(@"*******************定时器来了**********");
if (timeInt == 5) {
NSLog(@"定时器暂停");
dispatch_suspend(_timer); //
sleep(2);
dispatch_resume(_timer);
NSLog(@"定时器恢复");
}
if (timeInt == 10) {
dispatch_source_cancel(_timer);
_timer = nil;
NSLog(@"定时器消耗");
}
});
dispatch_resume(_timer);
}
六、任务组的使用
dispatch_group
当我们需要监听一个并发队列中,所有任务都完成了,就可以用到这个group,因为并发队列你并不知道哪一个是最后执行的,所以以单独一个任务是无法监听到这个点的,如果把这些单任务都放到同一个group,那么,我们就能通过dispatch_group_notify方法知道什么时候这些任务全部执行完成了。
- (void)dispatchGruopQueueTest {
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group=dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"组0");});
dispatch_group_async(group, queue, ^{NSLog(@"组1");});
dispatch_group_async(group, queue, ^{NSLog(@"组2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"gruop 组执行完毕");});
}
七、打断任务,插入任务
dispatch_barrier_async
此方法的作用是在并发队列中,完成在它之前提交到队列中的任务后打断,
单独执行其block,
并在执行完成之后才能继续执行在他之后提交到队列中的任务:
- (void)dispatchBarrierQueueTest {
dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"0");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"1");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"2");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"3");});
dispatch_barrier_async(concurrentDiapatchQueue, ^{sleep(1); NSLog(@"打断了。。。。");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"5");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"6");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"7");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"8");});
}
八、无序查找
dispatch_apply
这个方法用于无序查找,在一个数组中,我们能开启多个线程来查找所需要的值,
但是在遍历完成之前会阻塞主线程
- (void)dispathchApplyTest {
NSArray *array=[[NSArray alloc]initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6", nil];
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
});
NSLog(@"阻塞");// 将会在便利之后完成之后打印
}
九、队列的挂起,恢复
dispatch_suspend & dispatch_resume
队列挂起和恢复,
- (void)dispatchSuspendAndResumeTest {
dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.suspend_resume.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDiapatchQueue, ^{
for (int i=0; i<100; i++)
{
NSLog(@"%i",i);
if (i==50)
{
NSLog(@"-----------------------------------队列挂起");
dispatch_suspend(concurrentDiapatchQueue);
sleep(3);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_resume(concurrentDiapatchQueue);
NSLog(@"-----------------------------------队列恢复");
});
}
}
});
}
十、信号量
Semaphore 有三个方法
创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)
提高信号量
dispatch_semaphore_signal(信号量)
信号量的使用
举个例子
我们要下载很多文件,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。
用好信号量,可以达到合理分配CPU资源,程序也能得到优化。
- (void)dispatchSignalTest {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//为了让一次下载10个,初始信号量为10
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i <100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//每进来1次,信号量-1;进来10次后就一直hold住,直到信号量大于0;
dispatch_async(queue, ^{
NSLog(@"下载文件_%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);//由于这里只是log,所以处理速度非常快,我就模拟2秒后下载完成并信号量+1;
});
}
}
十一、单列
dispatch_once
这个函数一般是用来做一个真的单例,也是非常常用的
但是使用单列需要小心,否则会造成线程死锁
单列 A B C D
引用关系
A->B B->A 这样就会死锁,但是这种错误一般大家都不会发生,但是如果单列多了,可能就是下面这种情况,而且有时候你自己也不知道
A->B ->C->D ->A 这种情况就会造成单列死锁,所以,各位同学使用单列的时候,需要注意哦
+ (ViewController *)dispatchOnceTest {
static ViewController * instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
以上是本人的浅见,如有错误,望各位多多指正