一、什么是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自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。
三、GCD语音
1、Dispatch Objects
尽管GCD是纯c语言的,但它被组建成面向对象的风格。GCD对象被称为dispatch object。Dispatch object像Cocoa对象一样是引用计数的。使用dispatch_release和dispatch_retain函数来操作dispatch object的引用计数来进行内存管理。但主意不像Cocoa对象,dispatch object并不参与垃圾回收系统,所以即使开启了GC,你也必须手动管理GCD对象的内存。
Dispatch queues 和 dispatch sources(后面会介绍到)可以被挂起和恢复,可以有一个相关联的任意上下文指针,可以有一个相关联的任务完成触发函数。可以查阅“man dispatch_object”来获取这些功能的更多信息。
2、Dispatch Queues
GCD的基本概念就是dispatch queue。dispatch queue是一个对象,它可以接受任务,并将任务以先到先执行的顺序来执行。dispatch queue可以是并发的或串行的。并发任务会像NSOperationQueue那样基于系统负载来合适地并发进行,串行队列同一时间只执行单一任务。
GCD中有三种队列类型:
- The main queue: 与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。
- Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:高、中(默认)、低三个优先级队列。可以调用dispatch_get_global_queue函数传入优先级来访问队列。
- 用户队列: 用户队列 (GCD并不这样称呼这种队列, 但是没有一个特定的名字来形容这种队列,所以我们称其为用户队列) 是用函数
dispatch_queue_create
创建的队列。正因为如此,它们可以用来完成同步机制, 有点像传统线程中的mutex。
四、GCD语句
1、Dispatch Queues的生成可以有这几种方式:
dispatch_queue_t queue = dispatch_queue_create(
"com.dispatch.serial"
, DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create(
"com.dispatch.concurrent"
, DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0
);
(注意:需要注意的是,第一个参数是自定义的queue(默认优先级就是global queue的default),而不是系统的queue(global/main)。因为你不能给系统的queue设置权限。通过上面设置,serialQueue 就有了与globalQueue一样的优先级。其实这个函数不仅可以设置queue的优先级,还可以设置queue之间的层级结构。.官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。 )
通常,我们可以在global_queue中做一些long-running的任务,完成后在main_queue中更新UI,避免UI阻塞,无法响应用户操作:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
});
});
对上面自定义生成的线程队列,我们不能设置他的优先级,那要怎么设置呢?
void
dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue); //
它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB); //
那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:
dispatch_suspend(dispatchA); //
则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。
另一个作用:
dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL); // 当不声明队列类型时,可以设置优先级
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
dispatch_set_target_queue(serialQueue, globalQueue);// 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
综上所述,可以得出结论,自定义的线程队列,其实不能修改优先级,但是可以把队列的内容放到其他线程队列中(一般是系统的队列)来改变优先级。因此队列其实就是缓存的作用,不代表什么时候会被执行
2、Dispatch 执行:
dispatch_queue_t queue = dispatch_get_main_queue();
接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。
dispatch_async(queue, ^{
});
dispatch_sync(queue, ^{
});
实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。
如果queue
1
是一个串行队列的话,这段代码立即产生死锁:
dispatch_sync(queue
1
, ^{
dispatch_sync(queue
1
, ^{
......
});
......
});
那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0
), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
});
});
dispatch队列是线程安全的,可以利用串行队列实现锁的功能。
比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:
dispatch_queue_t queue
1
= dispatch_queue_create(
"com.dispatch.writedb"
, DISPATCH_QUEUE_SERIAL);
- (
void
)writeDB:(
NSData
*)data
{
dispatch_async(queue
1
, ^{
});
}
下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。
暂停和恢复
dispatch_suspend(queue)可以暂停队列中任务的执行
dispatch_resume(queue)可以继续执行被暂停的队列。
dispatch_suspend会挂起dispatch queue,但并不意味着当前正在执行的任务会停下来,这只会导致不再继续执行还未执行的任务。dispatch_resume会唤醒已挂起的dispatch queue。你必须确保它们成对调用。(如果你挂起了一个queue或者source,那么销毁它之前,必须先对其进行恢复。)
3、Dispatch Group 线程组方法:
线程组,是对队列的一种汇总,它能监控到其他的队列状况,并进行相应的操作
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, ^{
[NSThreadsleepForTimeInterval:6];
NSLog(@"group1 [NSThread sleepForTimeInterval:6];");
});
dispatch_group_async(group, queue, ^{
[NSThreadsleepForTimeInterval:3];
NSLog(@"group2 [NSThread sleepForTimeInterval:3];");
});
dispatch_group_async(group, queue, ^{
[NSThreadsleepForTimeInterval:1];
NSLog(@"group3 [NSThread sleepForTimeInterval:1];");
});
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
NSLog(@"等待全面执行完才执行");
});
3、Dispatch 其他方法:
void
dispatch_apply(
size_t
iterations, dispatch_queue_t queue,
void
(^block)(
size_t
));
// 可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void
dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@
"dispatch_async1"
);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@
"dispatch_async2"
);
});
dispatch_barrier_async(queue, ^{ // 等同串行queue(DISPATCH_QUEUE_SERIAL)
NSLog(@
"等待 前面两个执行完才执行"
);
[NSThread sleepForTimeInterval:0.5];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@
"等待dispatch_barrier_async 执行完才执行"
);
});
//延迟执行block
void
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
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
){
});
dispatch_once这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次!
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once });
本文参考自: http://blog.csdn.net/crycheng/article/details/22214617、http://blog.csdn.net/wildfireli/article/details/18668897,并对改文进行了补充说明。