什么是GCD与Dispatch Queue?
GCD:是Grand Central Dispatch缩写,是Apple公司封装的一个用于进行并发程序设计的一个API,它以C语言为基础,将线程的操作抽象为对队列的操作。
Dispatch Queue:Dispatch Queue是GCD中用到的队列类型,有串行队列、并发队列、主队列等。
GCD有什么用?
在进行iOS开发过程,我们经常需要对多个任务进行操作,这时就需要用到线程,iOS开发中对线程的操作多种多样,有PThreads、NSThread、NSOperation以及GCD。
Pthread:是类Unix系统中用来对线程进行操作的一组C语言写的API,可以在Unix、Mac OX S、Linux中使用,学习Linux下的C语言多线程会用到这个。
NSThread:是Apple对Pthread的抽象,NSThread对Pthread的抽象程度较低,在进行多线程开发时,依然需要进行对线程进行创建、使用、销毁等操作。
NSOperation Queue:Apple基于GCD封装的一种基于Queue操作的并发处理技术。
GCD:基于C语言封装的一个对任务进行操作的并发处理技术。
在封装的三种API中,Apple公司最为推崇GCD与NSOperation Queue,因为这两种方式让开发者的注意力转向如何对并发程序进行设计,不用关注线程到底是怎样执行任务的,提高了开发的效率。关于并发程序的设计可以查看官方文档Concurrency and Application Design,里面有对GCD、NSOperation Queue以及其它并发技术的详细讲解。
需要了解的知识
关于GCD,需要串行、并发和同步以及异步这些知识。在GCD中,串行和并发是用来形容Dispatch Queue的。
并发(concurrent):在操作系统中,同一时间段,有多个程序同时在处理器上执行,就像这些程序在同时进行一样。在单一CPU的机器上,并发技术基于时间片技术,而在多核机器上,并发技术基于多核技术的发展。在GCD中并发队列中的任务可以不用等前一个任务执行完就能执行。
串行(serial):既然并发是多个程序看起来在同时进行,那么串行就是多个程序排队一个一个执行。在GCD串行队列中,队列中的任务需要等出队的任务执行完后才能接着执行。
在GCD中,同步与异步是指任务之间的制约关系的。
同步:同步在操作系统中是指进程之间的制约关系。一个进程要等另一个进程执行完成后才能执行,而不是同时执行,是进程之间的执行次序的管理。例如,进程B需要用到进程A的结果才能执行,因此系统会先执行A然后在执行B,进程A与B的关系就是同步。同理,在GCD中,具有同步关系的两个任务,一个需要等另一个执行完后才能执行,某种程度上,同步的效果与串行相似。
异步:与同步的意思相反,既然同步是一个接一个执行,那么异步就是后一个任务不必等前一个任务执行完,即任务A执行的结果并不影响任务B,任务A与任务B不相关,没有确定的先后顺序。
Dispatch Queue类型
串行队列:任务按被加入到队列中的顺序依次执行,每次只从队中取出一个任务执行。
并发队列:同时执行一个或多个任务,但任务仍然是按它们被添加到队列中的顺序开始的。
主队列:是一个全局串行队列,它在应用程序的主线程上执行任务。因为它运行在您的应用程序的主线程上,主队列经常被用来作为应用程序的一个关键的同步点。
创建Dispatch Queue
-
串行队列
dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
参数一是队列名,用于Debug时进行跟踪,参数二是队列属性,创建串行队列,默认为0,或者设为DISPATCH_QUEUE_SERIAL。
- 并发队列
-
自定义并发队列
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
-
与创建串行队列一样,不过参数二为DISPATCH_QUEUE_CONCURRENT。
* 全局并发队列
dispatch_queue_t concur = dispatch_get_global_queue(ISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
参数一是全局并发队列的优先级,参数二为未来新特性预留,默认为0即可。全局并发队列分别有四个优先级:
DISPATCH_QUEUE_PRIORITY_HIGHT,
DISPATCH_QUEUE_PRIORITY_DEFAULT,
DISPATCH_QUEUE_PRIORITY_LOW,
DISPATCH_QUEUE_PRIORITY_BACKGROUND
-
主队列
dispatch_queue_t main = dispatch_get_main_queue();
队列与线程的关系
objc上一篇名为Concurrent Programming: APIs and Challenges的文章中的一张图很好的描述了队列与线程的关系。
主队列中的任务都是在主线程中执行的,自定义的串行队列和并发队列中的任务,最终会流入系统的全局队列,系统会从线程池中分配相应的队列去执行。
将任务添加到队列
在GCD中,任务通常是以block的形式添加到队列当中的,任务的添加有两种方式,dispatch_sync(同步)和dispatch_async(异步),这两种添加方式决定了队列中的任务之间的制约关系。
dispatch_sync
将block提交到队列中,并阻塞当前队列,直到block执行结束才回返回到当前队列,提交的任务之间以同步方式执行。需要注意的是,Apple关于dispatch_sync的API文档中有这样一句话,As an optimization, this function invokes the block on the current thread when possible。作为优化,dispatch_sync阻塞当前队列后,系统会让当前线程执行block语句。dispatch_async
不等提交到队列的block开始执行就立即返回到当前线程。该函数将block提交到队列后,系统会从线程池中分配线程依次执行队列中的block(并发队列分配的线程个数由系统的状态决定),这样就实现了异步的执行方式。
代码示例:
dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(serial, ^{
NSLog(@"%@-sync",[NSThread currentThread]);
});
dispatch_async(serial, ^{
NSLog(@"%@-async",[NSThread currentThread])
});
dispatch_main();
以上是对于GCD的基本使用,有关GCD的具体使用可以参考官方文档Dispatch Queue以及官方API参考Dispatch
通过例子我们来看一下,串行队列与并发队列的线程数目。
代码示例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++)
{
dispatch_sync(serial, ^{
NSLog(@"serial-%@-sync",[NSThread currentThread]);
});
dispatch_async(serial, ^{
NSLog(@"serial-%@-async",[NSThread currentThread]);
});
}
for(int i = 0; i < 5; i++)
{
dispatch_sync(concur, ^{
NSLog(@"concurrent-%@-sync",[NSThread currentThread]);
});
dispatch_async(concur, ^{
NSLog(@"concurrent-%@-async",[NSThread currentThread]);
});
}
dispatch_main();
}
return 0;
}
为了结果直观显示,将不测的部分注释后运行,结果如下:
-
串行队列同步添加
2016-08-14 18:20:30.313 GCD[798:89819] Hello, World! 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync Program ended with exit code: 0
-
串行队列异步添加
2016-08-14 18:24:36.657 GCD[830:92007] Hello, World! 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
-
并发队列同步添加
2016-08-14 18:28:53.662 GCD[862:94207] Hello, World! 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
-
并发队列异步添加
2016-08-14 18:29:39.743 GCD[872:94931] concurrent-<NSThread: 0x100500050>{number = 3, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94932] concurrent-<NSThread: 0x100700560>{number = 4, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94933] concurrent-<NSThread: 0x1007006f0>{number = 5, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94930] concurrent-<NSThread: 0x100403380>{number = 2, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94928] concurrent-<NSThread: 0x100500550>{number = 6, name = (null)}-async
对比以上结果,可以得出结论:
- 队列的类型决定所需要的线程的个数。串行队列和并发队列执行任务所需要的线程个数不同,串行队列只需要一个线程,并发队列需要多个线程(线程个数由系统决定)。
- dispatch_sync和dispatch_async决定了是否开启线程执行队列中的任务。dispatch_sync在当前线程中执行队列中的任务,dispatch_async从线程池中至少获取一个线程来,保证异步执行。
- 无论是串行队列,还是并发队列,使用dispatch_sync添加任务后,任务都是在当前线程中执行,这是由于Apple公司优化的结果。
- 对于串行队列使用dispatch_async,系统会新开一个线程来执行队列中的任务,并没有开多个线程,可以理解为因为任务一次只执行一个,若每个任务都创建一个线程运行,开销巨大,并不划算,干脆使用同一线程依次执行所有任务。
- 对于并发队列使用dispatch_async,由于队列中的任务都需要并发运行,如果只开一个线程,则队列中的任务之间相当于串行执行,所以开了多个线程,至于线程开多少个这由系统决定,例如将for循环的次数改为100,可以看到有多个任务使用同一线程。