本节重点是队列和任务的组合,不多说,先上图!!
C 语言 /管理线程不方便!
摘要:GCD 核心概念:
- 将
任务
添加到队列
,并且指定执行任务的函数
- 任务使用
block
封装- 任务的
block
没有参数也没有返回值
- 任务的
- 执行任务的函数
- 异步
dispatch_async
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行
block
的任务 -
异步
是多线程的代名词
- 同步
dispatch_sync
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前执行
block
的任务
- 异步
- 队列 - 负责调度任务
- 串行队列
- 一次只能"调度"一个任务
dispatch_queue_create("wpf_silence", NULL);
- 并发队列
- 一次可以"调度"多个任务
dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
- 主队列
- 专门用来在主线程上调度任务的队列
- 不会开启线程
- 在
主线程空闲时
才会调度队列中的任务在主线程执行 dispatch_get_main_queue();
- 串行队列
阶段性小结
- 开不开线程由
执行任务的函数
决定-
异步
开,异步
是多线程的代名词 -
同步
不开
-
- 开几条线程由
队列
决定-
串行队列
开一条线程 -
并发队列
开多条线程,具体能开的线程数量由底层线程池决定- iOS 8.0 之后,GCD 能够开启非常多的线程
- iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
-
- 组合总结:
- 异步函数 + 串行队列:开启一条子线程,任务按顺序执行
- 异步函数 + 并行队列:
只有二者结合才有意义
开启多条子线程,任务同时进行,线程可复用 - 同步函数 + 串行队列:不开线程,同步执行,在当前线程执行
- 同步函数 + 并行队列:不开线程,顺序执行
队列的选择
多线程的目的:将耗时的操作放在后台执行!
-
串行队列,只开一条线程,所有任务顺序执行
- 如果任务有先后执行顺序的要求
- 效率低 -> 执行慢 -> "省电"
- 有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"
-
并发队列,会开启多条线程,所有任务不按照顺序执行
- 如果任务没有先后执行顺序的要求
- 效率高 -> 执行快 -> "费电"
- WIFI,包月
实际开发中,线程数量如何决定?
- WIFI 线程数
6
条 - 3G / 4G 移动开发的时候,
2~3
条,再多会费电费钱!
一. 同步 & 异步
概念
- 同步
- 在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句,
同步
从上到下顺序执行
- 在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句,
- 异步
- 不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句(但是开辟线程会浪费时间),
异步
是多线程的代名词
- 不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句(但是开辟线程会浪费时间),
NSThread中的 同步 & 异步
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"start");
// 同步执行
// [self demo];
// 异步执行
[self performSelectorInBackground:@selector(demo) withObject:nil];
NSLog(@"over");
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
NSLog(@"demo 完成");
}
二. GCD 常用代码
异步执行任务
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touch--->begin");
// 1. 全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 任务
void (^task)() = ^ {
NSLog(@""async---->%@"", [NSThread currentThread]);
};
// 3. 指定执行任务的函数
// 异步执行任务 - 新建线程,在新线程执行 task
dispatch_async(queue, task);
NSLog(@"touch--->end");
/*
输出结果: 说明是另外开辟一条子线程进行异步任务,而不是在当前线层中进行,但是开线程会浪费时间
touch--->begin
touch--->end
async----><NSThread: 0x7fbfc8518190>{number = 2, name = (null)}
*/
}
注意:如果等待时间长一些,会发现线程的
number
发生变化,由此可以推断gcd 底层线程池
的工作
同步执行任务
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touch--->begin");
// 1. 全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 任务
void (^task)() = ^ {
NSLog(@"sync---->%@", [NSThread currentThread]);
};
// 3. 指定执行任务的函数
// 同步执行任务 - 不开启线程,在当前线程执行 task
dispatch_sync(queue, task);
NSLog(@"touch--->end");
/*
touch--->begin
sync----><NSThread: 0x7f99c8c03650>{number = 1, name = main}
touch--->end
*/
}
精简代码
- (void)gcdDemo2 {
for (int i = 0; i < 10; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@ %@", [NSThread currentThread], @"hello");
});
}
}
与 NSThread
的对比
- 所有的代码写在一起的,让代码更加简单,易于阅读和维护
-
NSThread
通过@selector
指定要执行的方法,代码分散 -
GCD
通过block
指定要执行的代码,代码集中
-
- 使用
GCD
不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期 - 如果要开多个线程
NSThread
必须实例化多个线程对象 -
NSThread
靠NSObject
的分类方法实现的线程间通讯,GCD
靠block
线程间通讯
- (void)gcdDemo3 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"耗时操作 %@", [NSThread currentThread]);
// 耗时操作之后,更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新 UI %@", [NSThread currentThread]);
});
});
}
以上代码是
GCD
最常用代码组合!
- 如果要在更新 UI 之后,继续做些事情,可以使用以下代码
- (void)gcdDemo4 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"耗时操作");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
NSLog(@"更新UI完毕");
});
}
网络下载图片
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%s %@", __FUNCTION__, [NSThread currentThread]);
// 1. 异步下载网络图片
NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/1f178a82b9014a901bef674aaa773912b21bee70.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2. 完成后更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
[self.imageView sizeToFit];
self.scrollView.contentSize = image.size;
});
});
}
三. 串行队列
特点
- 以
先进先出
的方式,顺序
调度队列中的任务执行 - 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务
队列创建
dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", NULL);
串行队列演练
- 触摸事件调用方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchBegin!", [NSThread currentThread]);
// 串行队列,同步方法
//[self serailSync];
// 串行队列,异步方法
[self serailAsync];
NSLog(@"touchEnd!--->%@", [NSThread currentThread]);
}
- 串行队列 同步执行
/**
提问:是否开线程?是否顺序执行?
*/
- (void)serailSync {
// 1. 创建一个串行队列
/*
参数一:队列的标识符号,一般是公司名称倒写
参数二:队列的类型
*/
dispatch_queue_t serailQueue = dispatch_queue_create("com.apple", DISPATCH_QUEUE_SERIAL);
// 2. 创建三个任务
void (^task1) () = ^(){
NSLog(@"task1--->%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2--->%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3--->%@", [NSThread currentThread]);
};
// 3. 将创建好的三个任务添加到串行队列中,这个队列就开始调用我们的任务
dispatch_sync(serailQueue, task1);
dispatch_sync(serailQueue, task2);
dispatch_sync(serailQueue, task3);
}
因为是串行队列,因此在当前线程(主线程)中从上到下顺序执行
应用场景:很少使用
/*
打印结果:
touchBegin!---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
task1---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
task2---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
task3---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
touchEnd!---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
*/
- 串行队列 异步执行
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/
- (void)serailAsync {
// 1. 创建一个串行队列
dispatch_queue_t serailQueue = dispatch_queue_create("com.apple", NULL);
// 2. 创建三个任务
void (^task1) () = ^(){
NSLog(@"task1--->%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2--->%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3--->%@", [NSThread currentThread]);
};
// 3. 将创建的任务添加到队列中
dispatch_async(serailQueue, task1);
dispatch_async(serailQueue, task2);
dispatch_async(serailQueue, task3);
/*
因为是异步方法,所以新建子线程执行,这里只开辟一条子线程
因为是串行,因此从上到下依次打印
应用场景:
耗时间,有顺序的场景
1.登录--->2.付费--->3.才能看
*/
/*
打印结果:
touchBegin!---><NSThread: 0x7fb191402480>{number = 1, name = main}
touchEnd!---><NSThread: 0x7fb191402480>{number = 1, name = main}
task1---><NSThread: 0x7fb19145f100>{number = 2, name = (null)}
task2---><NSThread: 0x7fb19145f100>{number = 2, name = (null)}
task3---><NSThread: 0x7fb19145f100>{number = 2, name = (null)}
*/
}
四. 并发队列
特点
- 以
先进先出
的方式,并发
调度队列中的任务执行 - 如果当前调度的任务是
同步
执行的,会等待任务执行完成后,再调度后续的任务 - 如果当前调度的任务是
异步
执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行
队列创建
dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", DISPATCH_QUEUE_CONCURRENT);
并发队列演练
- 调用方法的点击事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"bigin--->%@", [NSThread currentThread]);
// 并发队列,同步执行
[self conCurrentSync];
// 并发队列,异步执行
//[self conCurrentAsync];
NSLog(@"end--->%@", [NSThread currentThread]);
}
- 并发队列 同步执行
- (void)conCurrentSync {
// 1. 队列
/*
参数一:const char *label 队列的标识符,一般是公司域名的倒写
参数二:dispatch_queue_attr_t attr 队列的类型
*/
dispatch_queue_t q = dispatch_queue_create("com.apple", DISPATCH_QUEUE_CONCURRENT);
// 2. 创建三个任务
void (^task1) () = ^(){
NSLog(@"task1--->%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2--->%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3--->%@", [NSThread currentThread]);
};
// 3. 将任务添加到队列中
dispatch_sync(conCurrentQueue, task1);
dispatch_sync(conCurrentQueue, task2);
dispatch_sync(conCurrentQueue, task3);
/*
因为是同步执行,所以都在当前线程(主线程)执行,不会开辟新的线程
****** 遇到同步的时候,并发队列,还是依次执行,因此方法的优先级比队列的优先级高 ******
应用场景:开发中几乎不用
*/
/*
打印结果:
bigin---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
task1---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
task2---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
task3---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
end---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
*/
}
- 并发队列 异步执行
- (void)conCurrentAsync {
// 1. 队列
dispatch_queue_t q = dispatch_queue_create("com.apple", DISPATCH_QUEUE_CONCURRENT);
// 2. 创建三个block类型的任务
void (^task1) () = ^(){
NSLog(@"task1--->%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2--->%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3--->%@", [NSThread currentThread]);
};
// 3. 把这三个任务添加到并发队列中
dispatch_async(conCurrentQueue, task1);
dispatch_async(conCurrentQueue, task2);
dispatch_async(conCurrentQueue, task3);
/*
无序
因为是并发队列,所以任务都不在当前线程中执行,而是另外开辟子线程
另外开辟子线程需要耗时间,因为是异步执行,因此end 可以跳过还未执行完毕的三个任务
开N条子线程,是由底层可调度线程池来决定的,可调度线程池有一个可重用机制
应用场景:
当我们下载电影的时候,可以把片头、片尾、中间部分分开下载,等到都下载完毕后拼接一下就好了
*/
/*
打印结果:
bigin---><NSThread: 0x7ff159e050c0>{number = 1, name = main}
task1---><NSThread: 0x7ff159d01960>{number = 2, name = (null)}
end---><NSThread: 0x7ff159e050c0>{number = 1, name = main}
task3---><NSThread: 0x7ff159c03890>{number = 3, name = (null)}
task2---><NSThread: 0x7ff159f1e4c0>{number = 4, name = (null)}
*/
}
五. 全局队列
- 是系统为了方便程序员开发提供的,其工作表现与
并发队列
一致
全局队列 & 并发队列的区别
- 全局队列
- 没有名称
- 无论 MRC & ARC 都不需要考虑释放
- 日常开发中,建议使用"全局队列"
- 并发队列
- 有名字,和
NSThread
的name
属性作用类似 - 如果在 MRC 开发时,需要使用
dispatch_release(q);
释放相应的对象 -
dispatch_barrier
必须使用自定义的并发队列 - 开发第三方框架时,建议使用并发队列
- 有名字,和
全局队列 异步任务
- (void)globalAsync {
// 1. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 执行任务
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
运行效果与并发队列相同
参数
-
ios7中表示队列对任务调度的优先级,ios8中表示服务质量
- 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 后台优先级
-
- iOS 8.0(新增,暂时不能用,今年年底)
为未来保留使用的,应该永远传入0
结论:如果要适配 iOS 7.0 & 8.0,使用以下代码:
dispatch_get_global_queue(0, 0);
六. 主队列
特点
- 专门用来在主线程上调度任务的队列
- 不会开启线程
- 以
先进先出
的方式,在主线程空闲时
才会调度队列中的任务在主线程执行 - 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
队列获取
- 主队列是负责在主线程调度任务的
- 会随着程序启动一起创建
- 主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();
主队列演练
- 主队列,异步执行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self mainAsync];
[NSThread sleepForTimeInterval:1];
NSLog(@"over");
}
- (void)mainAsync {
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i < 10; ++i) {
dispatch_async(queue, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
NSLog(@"---> %d", i);
}
}
/*
使用场景:
当做了耗时操作回到主线程更新UI的时候,非他不可
*/
在
主线程空闲时
才会调度队列中的任务在主线程执行
- 主队列,同步执行
// MARK: 主队列,同步任务
#warning 有问题,不能用,因为主队列只会在主线程有空闲的时候才会调度,然后里面的任务才会执行,造成死锁、死等,主线程卡死
- (void)mainSync {
// 1. 队列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 同步
dispatch_sync(q, ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
主队列
和主线程
相互等待会造成死锁,死循环,原因如下:
- 如果在主线程中运用主队列同步,就是把任务放到了主线程的队列中
- 同步对于任务是立刻执行的,那么当吧第一个任务放进主队列时,他就会立马执行
- 可是主线程现在正在助理 syncMain 方法,任务需要等 syncMain 执行完才能执行
- syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个第三个任务
- 这样syncMain 方法就和第一个第一个任务互相等待,形成了死锁
七. 同步任务的作用
同步任务,可以让其他异步执行的任务,
依赖
某一个同步任务
例如:在用户登录之后,再异步下载文件!
/*
同步的作用:保证任务执行的先后顺序
先登陆,然后同时下载三部小视频
*/
- (void)execLongTimeOperation {
// 这句代码有无均可
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 1. 首先登陆,同步执行,在当前线程中执行
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"login--->%@", [NSThread currentThread]);
});
// 2. 下载电影,并发执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"dloadA--->%@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"dloadV--->%@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"dloadI--->%@", [NSThread currentThread]);
});
});
/*
打印结果:
login---><NSThread: 0x7fbd01e01c50>{number = 2, name = (null)}
dloadA---><NSThread: 0x7fbd01e01c50>{number = 2, name = (null)}
dloadV---><NSThread: 0x7fbd01d02580>{number = 3, name = (null)}
dloadI---><NSThread: 0x7fbd01d13e50>{number = 4, name = (null)}
*/
}
- 主队列调度同步队列不死锁
- (void)gcdDemo1 {
dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", DISPATCH_QUEUE_CONCURRENT);
void (^task)() = ^ {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"死?");
});
};
dispatch_async(queue, task);
}
主队列在
主线程空闲时
才会调度队列中的任务在主线程执行
八. 延迟操作
// MARK: - 延迟执行
- (void)execDispatchAfter {
NSLog(@"begin");
/**
从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码
参数
1. when 从现在开始,经过多少纳秒
2. queue 队列
3. block 异步执行的任务
dispatch_after 异步的
应用场景:动画,钟摆,动画进行中,到左右两边后停留一下继续进行
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
NSLog(@"finished--->%@", [NSThread currentThread]);
});
NSLog(@"----end-----%@", [NSThread currentThread]);
/*
打印结果证明是异步的:
begin
----end-----<NSThread: 0x7f8192e04150>{number = 1, name = main}
finished---><NSThread: 0x7f8192e04150>{number = 1, name = main}
*/
}
九. 一次性执行
有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”
// MARK: 一次性执行
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"一次性吗?");
});
NSLog(@"come here");
}
- dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的
- 以下代码用于测试多线程的一次性执行
- (void)demoOnce {
for (int i = 0; i < 10; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self once];
});
}
}
单例的特点
- 在内存中只有一个实例
- 提供一个全局的访问点
单例实现
// 使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
sharedSingleton
方法经常用到
十. 调度组
常规用法
/*
应用场景:
将三个视频完全下载完毕才能继续后续的操作,这里就需要用到调度组对任务进行监控
*/
- (void)execGroupDispatch {
NSLog(@"开始下载。。。--->%@", [NSThread currentThread]);
// 1. 创建一个调度组
dispatch_group_t group = dispatch_group_create();
// 2. 获取全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
// 3. 创建三个任务
void (^task1) () = ^(){
NSLog(@"正在下载片头--->%@", [NSThread currentThread]);
};
#warning enter 和 leave 这两个函数在调度组内部已经封装好了
/** 调度组实现原理 */
dispatch_group_enter(group);
void (^task2) () = ^(){
NSLog(@"正在下载中间部分--->%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
NSLog(@"中间部分已经下载完毕--->%@", [NSThread currentThread]);
/** 如果没有这句话,调度组不会识别已经结束,就不会执行拼接的操作 */
dispatch_group_leave(group);
};
void (^task3) () = ^(){
NSLog(@"正在下载片尾--->%@", [NSThread currentThread]);
};
// 4. 将队列和任务添加到调度组里面
/*
任务一:需要加入的调度组
任务二:执行代码的线程
参数三:需要执行的代码
*/
dispatch_group_async(group, globalQueue, task1);
dispatch_group_async(group, globalQueue, task2);
dispatch_group_async(group, globalQueue, task3);
// 5. 监听调度组内时间是否完成
/**
参数1:组
参数2:参数3在哪个线程里面执行
参数3:组内完全下载完毕之后,需要执行的代码
*/
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"将下载的三段视频拼接中,马上播放!--->%@", [NSThread currentThread]);
});
/*
快速添加到调度组:
dispatch_group_async(group, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务 1 %@", [NSThread currentThread]);
});
*/
/*
打印结果:
开始下载。。。---><NSThread: 0x7fa10ad06610>{number = 1, name = main}
正在下载中间部分---><NSThread: 0x7fa10ae0f710>{number = 3, name = (null)}
正在下载片头---><NSThread: 0x7fa10ae15650>{number = 2, name = (null)}
正在下载片尾---><NSThread: 0x7fa10ad251b0>{number = 4, name = (null)}
将下载的三段视频拼接中,马上播放!---><NSThread: 0x7fa10ad251b0>{number = 4, name = (null)}
*/
}
十一. GCD 栅栏
- 当一批任务需要异步执行,但是需要分成两组,第一组执行完毕后才能执行第二组。这时候就需要用到 GCD 的栅栏方法 dispatch_barrier_async
- (void)barrierGCDTest {
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 异步执行
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"barrierTest:并发异步1 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"barrierTest:并发异步2 %@",[NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"------------barrier------------%@", [NSThread currentThread]);
NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"barrierTest:并发异步3 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"barrierTest:并发异步4 %@",[NSThread currentThread]);
}
});
}
上面代码的打印结果如下,开启了多条线程,所有任务都是并发异步进行。但是第一组完成之后,才会进行第二组的操作。
barrierTest:并发异步1 <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步2 <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步1 <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步2 <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步1 <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步2 <NSThread: 0x60000026e480>{number = 6, name = (null)}
------------barrier------------<NSThread: 0x60000026e480>{number = 6, name = (null)}
******* 并发异步执行,但是34一定在12后面 *********
barrierTest:并发异步4 <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步3 <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步4 <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步3 <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步4 <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步3 <NSThread: 0x60000026e480>{number = 6, name = (null)}
十一. 定时源事件和子线程运行循环
- 触摸事件执行该方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 在后台执行该方法
[self performSelectorInBackground:@selector(subThreadRun) withObject:nil];
}
- 实现定时源方法
- (void)subThreadRun {
NSLog(@"%s--->%@", __func__, [NSThread currentThread]);
// 1. 创建计时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
/**
NSDefaultRunLoopMode 当拖动的时候,它会停掉
因为这种模式是互斥的
forMode:UITrackingRunLoopMode 只有输入的时候,它才会去执行定时器任务
// 2.将计时器添加到运行循环中,只有加入到运行循环中,才知道有这个操作
NSRunLoopCommonModes 包含了前面两种
*/
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 3. 一定要有这一步,因为子线程运行循环默认不开放
#warning 下载,定时源事件,输入源事件,如果放在子线程里面,如果想要它执行任务,就必须开启子线程的运行循环
CFRunLoopRun();
}
// 递增打印
- (void)timeEvent {
NSLog(@"%@--->%ld", [NSThread currentThread], i++);
}