一、GCD多线程的优缺点
- 优点:
- ①、易用:GCD比NSThread和NSOperationQueue简单易用。由于GCD基于work unit而非像NSThread那样基于运算,所以GCD可以控制诸如等待任务结束、监视文件描述符、周期执行代码以及工作挂起等任务。基于blick的协同导致它能极为简单得在不同代码作用域之间传递上下文;
- ②、效率:GCD被实现得如此轻量和优雅,使得它在很多地方比之大专门消耗资料的线程更实际且快递。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了;
- ③、性能:GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。
二、GCD简介
- ①、Grand Central Dispatch是一套底层API,提供了一种新的方法来运行并发程序编写。从基本功能上讲,GCD有一点像NSOperationQueue,他们都允许程序将任务切成为多个单一任务,然后提交至工作队列来并发地活着串行地执行。GCD比NSOperationQueue更底层更高效,并且它不是Cocoa框架的一部分。
- ②、并发任务会像NSOperationQueue那样基于系统负载来合适地并发进行,串行队列同一时间只执行单一任务。
- ③、GCD的API很大程度上基于block,当然,GCD也可以脱离block来使用,比如使用传统C机制提供函数指针和上下文指针。当配合block使用时,GCD非常简单易用能发挥最大能力。
三、Concurrent
又称为global dispatch queue,通过dispatch_get_global_queue获取,由系统创建三个不同优先级的dispatch queue。并行队列的执行顺序与其加入队列的顺序相同。
四、Serial
又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然他们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
serial qieies通过dispatch_queue_create创建,可以使用函数dispatch_retain和dispatch_release去增加或者减少引用计数。
五、Main dispatch queue
它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
六、GCD的几种种使用方法
1、dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个县城中处理这些操作,然后通过主线程跟新界面。
例如:全局并发队列的优先级
#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_DEFAULT, 0), ^{
// 耗时的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
});
});
系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列时全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
这里也用到了系统默认就有一个串行队列main_queue
dispatch_queue_t mainQ = dispatch_get_main_queue();
虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release
2、dispatch_group_async
dispatch_group_async可以实现监听一组人物是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后才通知界面说完成。该组中是并发执行的。例如:
- (void)gcdGroup{
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, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});
dispatch_release(group);
}
打印顺序:
2015-03-16 23:33:09.593 GCDDemo[959:49260] group1
2015-03-16 23:33:10.593 GCDDemo[959:49262] group2
2015-03-16 23:33:11.594 GCDDemo[959:49261] group3
2015-03-16 23:33:11.595 GCDDemo[959:49222] updateUi
3、dispatch_barrier_async
dispatch_barrier_async是在前面的任务执行结束后它才执行,并且它后面的任务等它执行完成后才会执行。例如:
- (void)gcdGroup{
dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
}
打印顺序如下:
2015-03-16 23:29:18.222 GCDDemo[930:47738] dispatch_async1
2015-03-16 23:29:20.223 GCDDemo[930:47740] dispatch_async2
2015-03-16 23:29:20.223 GCDDemo[930:47740] dispatch_barrier_async
2015-03-16 23:29:25.226 GCDDemo[930:47740] dispatch_async3
注意:DISPATCH_QUEUE_CONCURRENT可以使单个线程转变成多个线程
4、dispatch_apply
执行某个代码片段N次:
- (void)gcdGroup{
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});
}
5、dispatch_once
一次性执行,例如:
- (void)gcdGroup{
// 一次性执行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
}
6、dispatch_time_t
延迟N秒执行,例如:
- (void)gcdTime{
// 延迟2秒执行:
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
});
}
7、dispatch_set_target_queue
在后台执行动作处理Serial Dispatch Queue的生成方法如下:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
8、dispatch_suspend/dispatch_resume
当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再回复。例如:
//dispatch_suspend函数挂起指定的Dispatch Queue
dispatch_suspend(queue);
//dispatch_resume函数恢复指定的Dispatch Queue
dispatch_resume(queue);
9、Dispatch I/O
在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。现今的输入/输出硬件已经可以做到一次使用多个线程更快的并列读取。能实现这一功能就是Dispatch I/O和Dispatch Data
。
#七、同步、异步、并发、串行
同步和一部决定了要不要开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力;
- 异步:在新的线程中执行任务,具备开启新线程的能力;
并发和串行决定了任务的执行方式
- 并发:多个任务并发(同时)执行;
- 串行:一个任务执行完毕后,再执行下一个任务
八、串行队列
GCD中获取串行队列有两种
1、使用dispatch_queue_create函数创建串行队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);//队列名称,队列属性,一般用NULL即可
示例:
dispatch_queue_t queue = dispatch_queue_create("wendingding", NULL); // 创建
dispatch_release(queue); // 非ARC需要释放手动创建的队列
2、使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行,使用dispatch_get_main_queue()获得主队列
示例:
dispatch_queue_t queue = dispatch_get_main_queue();
九、并行队列
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建,使用dispatch_get_global_queue函数获得全局的并发队列。
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);//此参数暂时无用,用0即可。
示例:
第一个参数为优先级,这里选择默认的。获取一个全局的默认优先级的并发队列;
第二个参数展示没有用,传0即可;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列
十、各种队列的执行效果
示例:
1、用异步函数往并发队列中添加任务
- (void)viewDidLoad{
[super viewDidLoad];
//1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.添加任务到队列中,就可以执行任务//异步函数:具备开启新线程的能力
dispatch_async(queue, ^{
NSLog(@"下载图片1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);
}
打印:
2015-03-17 00:28:25.951 GCDDemo[1175:71900] 主线程----<NSThread: 0x7fac5a5280f0>{number = 1, name = main}
2015-03-17 00:28:25.951 GCDDemo[1175:71957] 下载图片1----<NSThread: 0x7fac5a472480>{number = 3, name = (null)}
2015-03-17 00:28:25.951 GCDDemo[1175:71959] 下载图片2----<NSThread: 0x7fac5a538040>{number = 4, name = (null)}
2015-03-17 00:28:25.951 GCDDemo[1175:71958] 下载图片2----<NSThread: 0x7fac5a474f60>{number = 2, name = (null)}
总结:同时开启三个线程
2、用异步函数往串行队列中添加任务
- (void)viewDidLoad
{
[super viewDidLoad];
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);
//创建串行队列
dispatch_queue_t queue= dispatch_queue_create("wendingding", NULL);
//第一个参数为串行队列的名称,是c语言的字符串
//第二个参数为队列的属性,一般来说串行队列不需要赋值任何属性,所以通常传空值(NULL)
//2.添加任务到队列中执行
dispatch_async(queue, ^{
NSLog(@"下载图片1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
//3.释放资源
// dispatch_release(queue);
}
打印:
2015-03-17 00:34:21.931 GCDDemo[1224:74016] 主线程----<NSThread: 0x7fa500d28340>{number = 1, name = main}
2015-03-17 00:34:21.932 GCDDemo[1224:74074] 下载图片1----<NSThread: 0x7fa500db6b00>{number = 2, name = (null)}
2015-03-17 00:34:21.932 GCDDemo[1224:74074] 下载图片2----<NSThread: 0x7fa500db6b00>{number = 2, name = (null)}
2015-03-17 00:34:21.932 GCDDemo[1224:74074] 下载图片2----<NSThread: 0x7fa500db6b00>{number = 2, name = (null)}
总结:只开启一个线程
3、用同步函数往并发队列中添加任务
- (void)viewDidLoad
{
[super viewDidLoad];
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);
//创建串行队列
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.添加任务到队列中执行
dispatch_sync(queue, ^{
NSLog(@"下载图片1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
}
打印:
2015-03-17 00:40:00.633 GCDDemo[1259:76311] 主线程----<NSThread: 0x7fb47be1b180>{number = 1, name = main}
2015-03-17 00:40:00.633 GCDDemo[1259:76311] 下载图片1----<NSThread: 0x7fb47be1b180>{number = 1, name = main}
2015-03-17 00:40:00.633 GCDDemo[1259:76311] 下载图片2----<NSThread: 0x7fb47be1b180>{number = 1, name = main}
2015-03-17 00:40:00.633 GCDDemo[1259:76311] 下载图片2----<NSThread: 0x7fb47be1b180>{number = 1, name = main}
总结:不会开启新的线程,并发队列失去了并发的功能
4、用同步函数往串发队列中添加任务
- (void)viewDidLoad
{
[super viewDidLoad];
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);
//创建串行队列
dispatch_queue_t queue= dispatch_queue_create("wendingding", NULL);
//2.添加任务到队列中执行
dispatch_sync(queue, ^{
NSLog(@"下载图片1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
}
打印:
2015-03-17 00:43:41.330 GCDDemo[1290:77759] 主线程----<NSThread: 0x7fe298c14320>{number = 1, name = main}
2015-03-17 00:43:41.331 GCDDemo[1290:77759] 下载图片1----<NSThread: 0x7fe298c14320>{number = 1, name = main}
2015-03-17 00:43:41.331 GCDDemo[1290:77759] 下载图片2----<NSThread: 0x7fe298c14320>{number = 1, name = main}
2015-03-17 00:43:41.331 GCDDemo[1290:77759] 下载图片2----<NSThread: 0x7fe298c14320>{number = 1, name = main}
总结:不会开启新的线程
补充:
- ①、凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。
- ②、GCD的数据类型在ARC的环境下不需要再做release。
- ③、CF(core Foundation)的数据类型在ARC环境下还是需要做release。