什么是GCD
Grand Central Dispatch或者GCD,是一套低层API,提供了一种新 的方法来进行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行。GCD比之NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分。
除了代码的平行执行能力,GCD还提供高度集成的事件控制系统。可以设置句柄来响应文件描述符、mach ports(Mach port 用于 OS X上的进程间通讯)、进程、计时器、信号、用户生成事件。这些句柄通过GCD来并发执行。
GCD的API很大程度上基于block,当然,GCD也可以脱离block来使用,比如使用传统c机制提供函数指针和上下文指针。实践证明,当配合block使用时,GCD非常简单易用且能发挥其最大能力。
你可以在Mac上敲命令“man dispatch”来获取GCD的文档。
GCD的优势
GCD提供很多超越传统多线程编程的优势:
易用: GCD比之thread跟简单易用。由于GCD基于work unit而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束、监视文件描述符、周期执行代码以及工作挂起等任务。基于block的血统导致它能极为简单得在不同代码作用域之间传递上下文。
效率: GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了。
性能: GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。
下面来看一下几种线程的生成方式(Dispatch Queues)
1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。
2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行
3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。
4. dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。
GCD中常见的队列函数
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延迟执行block
dispatch_after
常见的延迟方法,在主线程中不能使用sleep,就得dispatch_after出场啦
NSLog(@"我要睡2秒");
double delayInSeconds = 2.0;//延迟2秒
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"睡醒了");
});
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
dispatch_group
当我们需要监听一个并发队列中,所有任务都完成了,就可以用到这个group,因为并发队列你并不知道哪一个是最后执行的,所以以单独一个任务是无法监听到这个点的,如果把这些单任务都放到同一个group,那么,我们就能通过dispatch_group_notify方法知道什么时候这些任务全部执行完成了。
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(@"down");});
把3个log分别放在并发队列中,通过把这个并发队列任务统一加入group中,group每次runloop的时候都会调用一个方法dispatch_group_wait(group, DISPATCH_TIME_NOW),用来检查group中的任务是否已经完成,如果已经完成了,那么会执行dispatch_group_notify的block
打印效果
2017-08-25 20:04:50.352 GCD的简单使用[41803:888331] 2
2017-08-25 20:04:50.352 GCD的简单使用[41803:888328] 1
2017-08-25 20:04:50.352 GCD的简单使用[41803:888347] 0
2017-08-25 20:04:50.359 GCD的简单使用[41803:888246] down
dispatch_barrier_async(栅栏函数)
这个方法在并发队列中前面的任务完成后再执行dispatch_barrier_async,dispatch_barrier_async执行完成,才执行后面的任务
dispatch_queue_t que =dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(que, ^{NSLog(@"0");});
dispatch_async(que, ^{NSLog(@"1");});
dispatch_async(que, ^{NSLog(@"2");});
dispatch_async(que, ^{NSLog(@"3");});
dispatch_barrier_async(que, ^{NSLog(@"我是一个栅栏");});
dispatch_async(que, ^{NSLog(@"5");});
dispatch_async(que, ^{NSLog(@"6");});
dispatch_async(que, ^{NSLog(@"7");});
dispatch_async(que, ^{NSLog(@"8");});
输出结果
2017-08-25 20:15:12.706 GCD的简单使用[41903:899729] 0
2017-08-25 20:15:12.706 GCD的简单使用[41903:899727] 1
2017-08-25 20:15:12.706 GCD的简单使用[41903:899726] 2
2017-08-25 20:15:12.707 GCD的简单使用[41903:899744] 3
2017-08-25 20:15:13.708 GCD的简单使用[41903:899744] 我是一个栅栏
2017-08-25 20:15:13.709 GCD的简单使用[41903:899744] 5
2017-08-25 20:15:13.709 GCD的简单使用[41903:899727] 6
2017-08-25 20:15:13.709 GCD的简单使用[41903:899729] 7
2017-08-25 20:15:13.709 GCD的简单使用[41903:899726] 8
dispatch_apply
这个方法用于无序查找,在一个数组中,我们能开启多个线程来查找所需要的值,记得放到异步去执行,要不然会阻塞主线程。
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_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
});
});
NSLog(@"主线程");
打印结果
2017-08-25 20:22:19.744 GCD的简单使用[42000:907091] 阻塞
2017-08-25 20:22:19.744 GCD的简单使用[42000:907126] 0=0
2017-08-25 20:22:19.744 GCD的简单使用[42000:907187] 1=1
2017-08-25 20:22:19.744 GCD的简单使用[42000:907127] 2=2
2017-08-25 20:22:19.744 GCD的简单使用[42000:907186] 3=3
2017-08-25 20:22:19.745 GCD的简单使用[42000:907126] 4=4
2017-08-25 20:22:19.745 GCD的简单使用[42000:907187] 5=5
2017-08-25 20:22:19.745 GCD的简单使用[42000:907127] 6=6
dispatch_suspend & dispatch_resume
线程的挂起和开始,下载的时候比较常用
dispatch_queue_t queue =dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i=0; i<100; i++)
{
NSLog(@"%i",i);
if (i==50)
{
NSLog(@"----------------我要停下来歇会-------------------");
dispatch_suspend(queue);
sleep(5);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_resume(queue);
});
}
}
});
打印结果
2017-08-25 20:27:50.671 GCD的简单使用[42079:912731] 50
2017-08-25 20:27:50.671 GCD的简单使用[42079:912731] ----------------我要停下来歇会-------------------
2017-08-25 20:27:55.677 GCD的简单使用[42079:912731] 51
dispatch_semaphore_t
我们可以通过设置信号量的大小,来解决并发过多导致资源吃紧的情况,以单核CPU做并发为例,一个CPU永远只能干一件事情,那如何同时处理多个事件呢,聪明的内核工程师让CPU干第一件事情,一定时间后停下来,存取进度,干第二件事情以此类推,所以如果开启非常多的线程,单核CPU会变得非常吃力,即使多核CPU,核心数也是有限的,所以合理分配线程,变得至关重要,那么如何发挥多核CPU的性能呢?如果让一个核心模拟传很多线程,经常干一半放下干另一件事情,那效率也会变低,所以我们要合理安排,将单一任务或者一组相关任务并发至全局队列中运算或者将多个不相关的任务或者关联不紧密的任务并发至用户队列中运算,所以用好信号量,合理分配CPU资源,程序也能得到优化,当日常使用中,信号量也许我们只起到了一个计数的作用,真的有点大材小用。
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;这里一直-1,一直减到0
dispatch_async(queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);//由于这里只是log,所以处理速度非常快,我就模拟2秒后信号量+1;这里是一直+1 一直加到10
});
}
dispatch_once
啥都不说啦写个单例压压惊
static SingletonTimer * instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[SingletonTimer alloc] init];
});
return instance;
全局的并发队列dispatch_get_global_queue
可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务一:
dispatch_async(myQueue, ^{
NSLog(@"1");
});
//任务二:
dispatch_async(myQueue, ^{
NSLog(@"2");
});
//任务三:
dispatch_async(myQueue, ^{
NSLog(@"3");
});
打印结果
执行时间是顺序的1.2.3,处理结果的时间是根据自己处理的任务大小来显示时间
2017-08-25 20:49:13.157 GCD的简单使用[42284:933722] 1
2017-08-25 20:49:13.157 GCD的简单使用[42284:933693] 2
2017-08-25 20:49:13.157 GCD的简单使用[42284:933692] 3
dispatch_set_target_queue转换队列
/**
dispatch_set_target_queue
通过dispatch_set_target_queue函数可以设置一个dispatch queue的优先级,或者指定一个dispatch source相应的事件处理提交到哪个queue上。
它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那么dispatchA上还未运行的block会放到dispatchB上,然后由dispatchB来进行管理运行。
**/
dispatch_set_target_queue(serialQ, globalQ);
dispatch_group_wait等待
dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。
dispatch_group_t group = dispatch_group_create();
//注意queue为DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL时当前的线程
dispatch_queue_t queue = dispatch_queue_create([@"queue" UTF8String], DISPATCH_QUEUE_SERIAL);
//每个dispatch_group_enter对应一个dispatch_group_leave完成group内所有的任务则发送通知
//进入group
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
//离开group
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"任务已经全部完成啦after group wait");
dispatch_group_notify(group, queue, ^{
NSLog(@"是的确实完成啦allGroupFinish");
});
打印结果
2017-08-25 21:10:17.743 GCD的简单使用[42425:950722] task1~~~~~currentThread=<NSThread: 0x608000262880>{number = 3, name = (null)}~~~~~mainThread=<NSThread: 0x600000079d80>{number = 1, name = (null)}
2017-08-25 21:10:17.743 GCD的简单使用[42425:950722] task2~~~~~currentThread=<NSThread: 0x608000262880>{number = 3, name = (null)}~~~~~mainThread=<NSThread: 0x600000079d80>{number = 1, name = (null)}
2017-08-25 21:10:17.743 GCD的简单使用[42425:950681] 任务已经全部完成啦after group wait
2017-08-25 21:10:17.743 GCD的简单使用[42425:950722] 是的确实完成啦allGroupFinish