想了解NSOperation与GCD的区别可参考iOS多线程之NSOperation及简单练习
文章内容较长,介绍下主要的目录
一、GCD介绍
二、任务的执行:同步、异步与栅栏
三、队列
四、代码使用示例
五、一次性执行及单例创建
六、延迟操作
七、栅栏调度和线程通信示例
八、调度组(dispatch_group)
九、快速迭代(类似for循环)
十、信号量(dispatch_semaphore)
其他的CGD相关内容我会慢慢补充的~
一、GCD介绍
1.简介
Grand Central Dispatch,既大名鼎鼎的狗艹的.它是苹果为多核的并行运算提出的解决方案,会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是会自动管理线程的生命周期(创建线程、调度任务、销毁线程),只需要告诉GCD要执行什么任务,不需要编写任何管理代码。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便.
2.GCD的核心概念
2.1 GCD中的主要核心概念是任务与队列
任务:执行什么操作
队列:用来存放任务
2.2 简要执行流程和说明:
1 将任务添加到队列,并且指定执行任务的函数
2 任务使用 block 封装
任务的 block 没有参数也没有返回值3 执行任务的函数
-
异步 dispatch_async
不用等待当前语句执行完毕,就可以执行下一条语句
会开启线程执行 block 的任务
异步是多线程的代名词
同步 dispatch_sync
必须等待当前语句执行完毕,才会执行下一条语句
不会开启线程
在当前执行 block 的任务
4 队列 - 负责调度任务
串行队列
并发队列
主队列
全局队列
3.GCD与NSThread的对比
-
所有的代码写在一起的,让代码更加简单,易于阅读和维护
NSThread 通过 @selector 指定要执行的方法,代码分散。
GCD 通过 block 指定要执行的代码,代码集中。
使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期。
如果要开多个线程 NSThread 必须实例化多个线程对象。
NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block。
二、任务的执行:同步、异步与栅栏
同步(dispatch_
sync
):任务执行时,只能在当前线程执行任务,不具备开启其他线程的能力
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步 (dispatch_
async
):具备开启新线程的能力,能将任务放在新线程中执行
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
栅栏 (dispatch_barrier_async):
功能:拦截前面的任务,等前面任务全部执行完毕后才会执行栅栏的任务,待栅栏任务执行完毕后才会继续执行后面的任务
使用方法:
使用栅栏,就不就能使用全局队列(苹果对全局队列内部进行了修改)且所有的任务都必须添加到同一队列中,栅栏才能起到拦截作用
详情请看七的示例代码
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block)
三、队列
1.串行队列
特点:
以先进先出的方式,顺序调度队列中的任务执行
无论队列中指定执行任务的方式是同步还是异步,都只会等待前一个任务执行完成后再被调度
创建方式:
dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("自定义线程名", NULL);
- 注意:
当使用同步执行任务的方式(sync)往串行队列中添加任务,会卡主当前的串行队列,造成死锁
2.并发队列
特点:
以随机的方式并发调度队列中的任务执行
若是以同步的方式执行任务,会等待任务执行完成后,再调度队列中的其他任务(既在同步的方式下,并发功能不会生效)
若是以异步的方式执行任务,只要底层线程池中有可用的资源,就会直接执行队列中的其他任务
创建方式:
dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_CONCURRENT);
创建串行和并发队列的函数参数:
第一个参数: 队列的名称
第二个参数: 告诉系统需要创建一个并发队列还是串行队列
-DISPATCH_QUEUE_SERIAL 串行
-DISPATCH_QUEUE_CONCURRENT 并发
3.主队列
特点:
专用用在主线程上调度任务的队列
是一种系统提供的特殊串行队列
如果当前主线程正在执行任务,那么主队列中所添加的任务就不会被调度
主队列只需要获取,不用手动创建
获取方式:
dispatch_queue_t queue = dispatch_get_main_queue();
4.全局队列
特点:
是系统提供的一种并发队列,以供全局使用
无需创建,可直接获取
若要使用栅栏阻塞任务,就不能使用全局队列,应自己创建并发队列
获取方式:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 全局队列参数:
/* 第一个参数:服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级
iOS 8.0(新增)
○ QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
○ QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
○ QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
○ QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
○ QOS_CLASS_BACKGROUND 0x09, 后台
○ QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
iOS 7.0
○ DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
○ DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
○ DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
○ DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级 为未来保留使用的,应该永远传入0
第二个参数:无用,传入0既可
结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0);
*/
四、代码使用示例
#import "ViewController.h"
@implementation ViewController
/*
如果是在子线程中调用 同步函数 + 主队列, 那么没有任何问题
*/
- (void)syncExceptMain
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
// 该block会在子线程中执行
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 该block会在主线程执行
NSLog(@"%@", [NSThread currentThread]);
});
});
}
/*
如果是在主线程中调用同步函数 + 主队列, 那么会导致死锁
导致死锁的原因:
若在主线程中执行sync函数,此时sync内的block在等待主线程执行完毕,而sync本身有在被主线程执行,导致双发都无法执行完毕,造成死锁。
*/
- (void)syncMain
{
NSLog(@"%@", [NSThread currentThread]);
// 主队列:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
//此时内部代码在等待主线程执行完毕,而主线程又在执行该sync操作,从而造成死锁
NSLog(@"----------");
NSLog(@"%@", [NSThread currentThread]);
});
}
/*
异步 + 主队列 : 任务在主线程中执行,且异步失效,只会同步执行
*/
- (void)asyncMain
{
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
/*
同步 + 并发 : 不开启新的线程,只会在当前线程顺序执行
*/
- (void)syncConCurrent
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"任务1 == %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2 == %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3 == %@", [NSThread currentThread]);
});
}
/*
同步 + 串行: 不开启新的线程,只会在当前线程顺序执行
*/
- (void)syncSerial
{
dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", NULL);
dispatch_sync(queue, ^{
NSLog(@"任务1 == %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2 == %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3 == %@", [NSThread currentThread]);
});
}
/*
异步 + 串行:会开启新的线程
但是只会开启一个新的线程
*/
- (void)asynSerial
{
/*
能够创建新线程的原因:我们是使用"异步"函数调用
只能创建1个子线程的原因:我们的队列是串行队列
*/
dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务1 == %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 == %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3 == %@", [NSThread currentThread]);
});
}
/*
异步 + 并发 : 会开启多个线程并发执行
*/
- (void)asynConcurrent
{
dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务1 == %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 == %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3 == %@", [NSThread currentThread]);
});
}
@end
/*
两个注意点:
当调用同步函数时,同步函数范围后的代码只能在同步函数全部执行完毕后执行;
当调用异步函数时,异步函数范围后的代码不用等函数执行完毕就会执行
*/
同步执行 | 异步执行 | |
---|---|---|
串行队列 | 不开线程,在当前线程,顺序执行 | 开1条线程,顺序执行 |
并发队列 | 不开线程,在当前线程,顺序执行 | 开多条线程,一起执行 |
主队列 | 不开线程,会造成死锁 | 不开线程,在主线程空闲的时候执行 |
全局队列 | 不开线程,在当前线程,顺序执行 | 开多条线程,一起执行 |
五、一次性执行及单例创建
使用
dispatch_once
函数让某段代码在程序运行时只执行一次(通过函数内部的锁来保证线程安全)主要作用:实现单例模式
// 使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
// 模拟dispatch_once来实现单例
// 使用互斥锁实现单例
+ (instancetype)sharedSync {
static id syncInstance;
@synchronized(self) {
if (syncInstance == nil) {
syncInstance = [[self alloc] init];
}
}
return syncInstance;
}
//两种方法的性能对比
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
long largeNumber = 1000 * 1000;
// 测试互斥锁
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSync];
}
NSLog(@"互斥锁: %f", CFAbsoluteTimeGetCurrent() - start);
// 测试 dispatch_once
start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i){
[Singleton sharedSingleton];
}
NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}
//结果表明用dispatch_once方法来实现单例模式效率远高与用互斥锁模拟实现
六、延迟操作
- 通过
dispatch_after
函数实现延缓代码执行
#pragma mark - 延迟执行
- (void)delay {
/**
方法解释:
从现在开始,经过多少纳秒,由"队列"调度 异步执行 block 中的代码
参数
1. when 从现在开始,经过多少纳秒
2. queue 队列
3. block 异步执行的任务
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
void (^task)() = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 主队列
dispatch_after(when, dispatch_get_main_queue(), task);
// 全局队列
// dispatch_after(when, dispatch_get_global_queue(0, 0), task);
// 串行队列
// dispatch_after(when, dispatch_queue_create("itheima", NULL), task);
NSLog(@"come here");
}
七、栅栏调度和线程通信示例
首先先配置下plist文件
- (void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("队列名", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue2 = dispatch_queue_create("队列名", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 1.开启一个新的线程下载第一张图片
dispatch_async(queue, ^{
NSURL *url = [NSURL URLWithString:@"http://www.shaimn.com/uploads/allimg/150509/1-150509233453.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
image1 = image;
NSLog(@"图片1下载完毕");
});
// 2.开启一个新的线程下载第二张图片
dispatch_async(queue, ^{
NSURL *url = [NSURL URLWithString:@"http://m.818today.com/imgsy/image/2016/0215/6359115208730406558041085.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
image2 = image;
NSLog(@"图片2下载完毕");
});
// 3.开启一个新的线程, 合成图片
// 栅栏
dispatch_barrier_async(queue, ^{
// 图片下载完毕
NSLog(@"%@ %@", image1, image2);
// 1.开启图片上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
// 2.将第一张图片画上去
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
// 3.将第二张图片画上去
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
// 4.从上下文中获取绘制好的图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// 5.关闭上下文
UIGraphicsEndImageContext();
// 6.回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = newImage;
});
NSLog(@"栅栏执行完毕了");
});
dispatch_async(queue, ^{
NSLog(@"---------");
});
dispatch_async(queue, ^{
NSLog(@"---------");
});
dispatch_async(queue, ^{
NSLog(@"---------");
});
}
八、调度组
功能:dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。调度组能实现的栅栏也可实现。
代码示例:异步线程中,下载两种图片,在dispatch_group_notify通知中说明图片下载完-> 合并图片,合并图片结束后 ->再回到主线程中更新UI
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s", __func__);
dispatch_queue_t queue = dispatch_queue_create("队列名", DISPATCH_QUEUE_CONCURRENT);
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
dispatch_group_t group = dispatch_group_create();
// 1.开启一个新的线程下载第一张图片
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:@"http://www.shaimn.com/uploads/allimg/150509/1-150509233453.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
image1 = image;
NSLog(@"图片1下载完毕");
});
// 2.开启一个新的线程下载第二张图片
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:@"http://m.818today.com/imgsy/image/2016/0215/6359115208730406558041085.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
image2 = image;
NSLog(@"图片2下载完毕");
});
// 3.开启一个新的线程, 合成图片
// 只要将队列放到group中, 队列中的任务执行完毕, group就会发出一个通知
dispatch_group_notify(group, queue, ^{
NSLog(@"%@ %@", image1, image2);
// 1.开启图片上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
// 2.将第一张图片画上去
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
// 3.将第二张图片画上去
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
// 4.从上下文中获取绘制好的图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// 5.关闭上下文
UIGraphicsEndImageContext();
// 4.回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = newImage;
});
});
}
调度组的实现原理:
#pragma mark - dispatch_group_async 的实现原理
//在终端输入命令:man dispatch_group_async可得到如下信息
/*
The dispatch_group_async() convenience function behaves like so:
void
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_retain(group);
dispatch_group_enter(group);
dispatch_async(queue, ^{
block();
dispatch_group_leave(group);
dispatch_release(group);
});
}
*/
// MARK: - 仿照上述信息来实现group原理
- (void)group2 {
// 1. 调度组
dispatch_group_t group = dispatch_group_create();
// 2. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 3. 将任务添加到队列
// 进入群组
dispatch_group_enter(group);
dispatch_async(q, ^{
NSLog(@"任务 1 %@", [NSThread currentThread]);
// 离开群组
dispatch_group_leave(group);
});
// 进入群组
dispatch_group_enter(group);
dispatch_async(q, ^{
NSLog(@"任务 2 %@", [NSThread currentThread]);
// 离开群组
dispatch_group_leave(group);
});
// 进入群组
dispatch_group_enter(group);
dispatch_async(q, ^{
NSLog(@"任务 3 %@", [NSThread currentThread]);
// 离开群组
dispatch_group_leave(group);
});
// 4. 监听所有任务完成
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"OVER %@", [NSThread currentThread]);
// });
// dispatch_group_wait 是同步的,DISPATCH_TIME_FOREVER参数表示一直等待到前面执行完毕
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 5. 判断异步
NSLog(@"come here");
}
九、快速迭代(类似for循环)
- 功能:使用dispatch_apply函数能进行快速迭代遍历,但是它和for循环又有些不同,for是按顺序循环,而_apply函数是随机调度
for (int index = 0;index < 100;index ++){
//在当前线程执行100次代码, index是从0到99
}
dispatch_apply(100, 队列queue, ^(size_t index){
//开启多个线程来执行100次代码,index顺序不确定
//相比于for循环执行的效率高
});
/*
第一个参数: 设置要执行几次
第二个参数: 决定第三个参数的block在哪个线程中执行
第三个参数: 调用的代码,其中参数i
*/
十、信号量(dispatch_semaphore)
功能:使用dispatch_semaphore_signal使信号量资源+1,设置dispatch_semaphore_wait等待来判断信号量是否为0,不为0时使信号量资源-1并返回,为0时继续执行,从而达到线程同步的目的和同步锁一样能够解决资源抢占的问题。
//通过对象方法直接返回数组(将从网络获取数据的过程要在子线程中)
- (__kindof NSArray *)getData{
//参数0表示一开始创建的信号量内部是没有资源的
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//创建用来存放数据的数组
NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];
//用全局队列来加载数据
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<10; i++) {
//模拟加载延时
[NSThread sleepForTimeInterval:1.0];
[array addObject:[NSNumber numberWithInt:i]];
}
//发送信号,使信号量资源数+1
dispatch_semaphore_signal(semaphore);
});
//信号等待,判断信号量资源数,不为0时就阻塞当前线程,并使资源数-1,循环执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return array;
}
调度组也可以实现上述功能
- (__kindof NSArray *)getData{
NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1.0];
for (int i=0; i<10; i++) {
[array addObject:[NSNumber numberWithInt:i]];
}
});
//阻塞当前线程,直到group的内容全部执行完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return array;
}
GCD和NSOperation的一些区别
- GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
- 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
- NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
- 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
- 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。