多线程 GCD 详解

来源:互联网 发布:js 多维数组 编辑:程序博客网 时间:2024/06/15 19:49

打开任务管理器总会看到”进程””线程”.用给孩子喂奶这个例子形容多线程还是比较贴切的,当你拿一个奶瓶同时给几个孩子喂奶时,如果你的速度足够快,在几个孩子之间迅速切换,就会做到让几个孩子都能吃上奶,效率也就提高了,多线程也是如此,同一时间, CPU 只能处理一条线程,做一件事情,这显然不能满足我们的需求,多线程并发,其实是 CPU 快速的在多条线程之间切换,如果 CPU 调度线程的时间足够快,就造成了多线程同时执行的假象.如果线程非常多, CPU 会在 N 多线程之间调度,CPU 会累死,每条线程被执行的频率会降低,程序就会卡顿
什么是 GCD?
全称 Grand Central Dispatch ,可译为牛逼的中枢调度器,注意它不是进程也不是线程,就是派发任务的工作单元,纯 C 语言,提供了非常多强大的函数
GCD 的优势?
GCD 是苹果公司为多核的并行运算提供的解决方案
GCD 会自动利用更多的 CPU 内核(比如双核,四核)
GCD 会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理的代码,所以 GCD 的思想就是把任务加入队列 任务有同步函数添加和异步函数添加 队列有串行和并发两种

GCD 有两个核心概念:
1>任务:执行什么操作 用同步函数和异步函数往队列添加任务
同步函数 dispatch_sync 不会开启新的线程 异步函数 dispatch_async 开启新的线程在串行队列开启一个新的线程 在并行队列开启 N个线程
2> 队列: 用来存放任务,将任务添加到队列中, GCD 会自动将队列中的任务取出,放到对应的线程中执行 队列不会直接决定是否开启新的线程,只会决定任务的执行方式 是一个接一个 还是同时执行 当串行队列时只会开启一个新的线程,并发队列时会根据 CPU 的状态开启多个线程

GCD 有两个用来执行任务的函数: dispatch_async(dispatch_queue_t queue, dispatch_block_t block)和 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)
同步与异步的区别:同步在当前线程中执行 异步在另一条线程中执行

队列可以分为两大类型
(1)并发队列 Concurrent Diapatch Queue
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发功能只有在异步函数(dispatch_async)下才有效
(2)串行队列(Serial Diapatch Queue)
让任务一个接一个的执行 不会创建新的线程

注意:四个比较容易混淆的术语:
同步和异步决定了是否开启新的线程
并行和串行决定了任务的执行方式,并发多个任务同时执行 串行一个接一个执行任务

GCD 中获得串行队列有两种途径
1> 使用 dispatch_queue_creat 函数创建
dispatch_queue_t 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 自带的一种特殊的串行队列,放在主队列中的任务,都会放在主线程中执行,所以不能加入一些耗时的操作,一般都是一些 UI 相关的操作

使用 dispatch_get_main_queue()获得主队列

dispatch_queue_t queue = disqueue_get_main_queue();

并发队列

GCD 默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建

使用dispatch_get_global_queue 函数获得全局的并发队列第一个参数是优先级,第一个参数传入0,现在用不上

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 // 后台

各种队列的执行效果
代码示例 :用异步函数往并发队列中添加任务
- (void)viewDidLoad {
[super viewDidLoad];
// 获取全局的并发队列,由苹果默认提供,无需创建
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 添加任务到队列中,就可以执行任务;添加异步函数,具备创建线程的能力
dispatch_async(queue, ^{
NSLog(@”并发 block1——%@”,[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@”并发 block2 ——-%@”,[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@”并发 block——–%@”,[NSThread currentThread]);
});
// 打印主线程
NSLog(@”主线程—–%@”,[NSThread mainThread]);
打印结果如下:说明同时开启了三个子进程
2015-09-18 16:33:00.457 gcd[1272:91186] 主线程—–{number = 1, name = main}
2015-09-18 16:33:00.457 gcd[1272:91493] 并发 block2 ——-{number = 3, name = (null)}
2015-09-18 16:33:00.457 gcd[1272:91495] 并发 block1——{number = 2, name = (null)}
2015-09-18 16:33:00.457 gcd[1272:91492] 并发 block——–{number = 4, name = (null)}

代码示例:往串行队里中添加并行任务 会开启新的线程 只会开启一个线程

// 打印主线程
NSLog(@”主线程—–%@”,[NSThread mainThread]);
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, NULL);
dispatch_async(queue, ^{
NSLog(@”并发 block1——%@”,[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@”并发 block2 ——-%@”,[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@”并发 block——–%@”,[NSThread currentThread]);
打印结果:2015-09-18 16:51:26.034 gcd[1382:97375] 主线程—–{number = 1, name = main}

2015-09-18 16:51:26.035 gcd[1382:97470] 并发 block1——{number = 2, name = (null)}

2015-09-18 16:51:26.035 gcd[1382:97470] 并发 block2 ——-{number = 2, name = (null)}

2015-09-18 16:51:26.036 gcd[1382:97470] 并发 block——–{number = 2, name = (null)}

代码示例:用同步函数往并发队列中添加任务 不会开启新的线程,并发队列失去了并发功能
NSLog(@”主线程—-%@”,[NSThread mainThread]);
25
26 //创建串行队列
27 dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
28
29
30 //2.添加任务到队列中执行
31 dispatch_sync(queue, ^{
32 NSLog(@”下载图片1—-%@”,[NSThread currentThread]);
33 });
34 dispatch_sync(queue, ^{
35 NSLog(@”下载图片2—-%@”,[NSThread currentThread]);
36 });
37 dispatch_sync(queue, ^{
38 NSLog(@”下载图片3—-%@”,[NSThread currentThread]);
39 });
示例代码 : 用同步函数往串行队列中添加任务 这个并没有什么用处,肯定不会开辟新的线程,开启新的线程是由同步异步函数决定的,串行和并行队列决定了任务的执行方式,是一个一个执行还是几个任务同时执行
NSLog(@”主线程—-%@”,[NSThread mainThread]);
27
28 //创建串行队列
29 dispatch_queue_t queue= dispatch_queue_create(“wendingding”, NULL);
30
31 //2.添加任务到队列中执行
32 dispatch_sync(queue, ^{
33 NSLog(@”下载图片1—-%@”,[NSThread currentThread]);
34 });
35 dispatch_sync(queue, ^{
36 NSLog(@”下载图片2—-%@”,[NSThread currentThread]);
37 });
38 dispatch_sync(queue, ^{
39 NSLog(@”下载图片3—-%@”,[NSThread currentThread]);
40 });

一.主队列介绍:主队列是和
苹果提供了三种基本的多线程方法
在 IOS 所有实现多线程方案, GCD 应该是最有魅力的,因为 GCD 本身是苹果公司为多核的并行运算提出的解决方案. GCD 在工作时会自动利用更多的处理器核心, GCD 是 Grand Central Dispatch他是基于 C 语言的.如果使用 GCD, 完全由系统管理线程,我们不需要编写线程代码,只需要定义想要执行的任务,然后添加到适当的调度队列, GCD 会负责创建线程和调度你的任务,系统直接提供线程管理
首先了解一下进程和线程,一般进程只负责分配内存,线程是进程的执行单元,一个进程至少有一个线程,并且至少有一个主线程,一般情况下把关于 UI 方面的操作放在主线下操作,其他耗时操作放在子线程下操作,另外就涉及到了队列和操作,在 GCD 情况下,苹果想要把我们从线程中解脱出来,从 MAC OS上移植过来的这个多线程管理系统,翻译过来就是大中央调度, 是方便多核开发的,系统自动开辟线程 回收线程,另外需要提一点,子线程是不能够杀掉的,只能够暂停让它暂时休眠一段时间,最后还是需要执行完毕由系统回收,一个主线程的大小是1M, 开辟的子线程是512K.GCD 仍然是最受欢迎的一种多线程开发方式,尤其是同步加并发的处理方式是最常用的,既能控制任务的执行顺序,又能并发处理.GCD 的中心思想就是把操作加入队列中,这里提到了队列,它是 GCD 的和核心理念:将长期运行的任务拆分成多个工作单元,并将这些单元添加到队列中,系统会为我们管理这些队列,为我们在多个线程上执行工作单元,系统提供了许多预定义的队列,包括可以保证始终在主线程上执行工作的队列,也可以创建自己的 dispatch_queue,而且可以创建任意多个, GCD 的 dispatch_queue 严格遵循 FIFO(先进先出)原则,串行或并发的执行,但是是按照顺序的
(1)serial dispatch_queue 串行队列 一次只能执行一个任务,当前任务完成才可以开始下一个任务
(2)concurrent dispatch_queue 并行队列 尽可能多的启动任务并发执行,具体开辟多少线程由系统决定 ,不需要我们手动参与
(3)添加任务到 queue 要执行一个任务,你需要将它添加到一个适当的 dispatch queue ,你可以单个或者按组添加,也可以同步或者异步的执行一个任务,一旦进入到 queue,queue会负责尽快的处理你的任务,一般可以用一个 block 块来封装任务内容:
1>异步添加任务
你可以同步或者异步的添加一个任务到 queue,尽可能的使用 dispatch_async 函数异步的调度任务,因为添加任务到 queue 中时,无法确定这些代码什么时候这行.因此异步的添加 block 或函数,可以让你立即调度这些代码的执行,然后调用线程去做其他事情.特别是应用主线程一定要异步的 dispatch 任务,这样才能及时的响应用户事件
2>同步添加任务
少数时候你可能希望同步添加任务到 queue,以避免竞争条件或者其他同步错误,使用 dispatch_sync 同步的添加任务到 queue, 这两个函数会阻塞当前调用线程,直到相应任务完成执行 注意: 绝对不要再任务中调用 dispatch_sync,并同步调度新任务到当前正在执行的 queue.对于串行 queue 这一点特别重要,这样做肯定会导致死锁,而并发 queue 也应该避免这样做,总的来说在一个串行队列调用同步任务肯定导致死锁

1>NSThread
2>NSoperation NSOperation 实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或穿行的方式执行这个操作
NSOperation 本身是个抽象基类,这点有点像 NSObject,因此必须使用它的子类,使用 NSOperation 的子类的方式有两种:
Foudation 框架提供了两个具体的子类直接供我们使用: NSInvocationOperation 和 NSBlockOperation
NSOperation 调用 start 方法即可开始执行操作,NSOperation 对象默认按同步方式执行, 也就是在调用 start 那个线程中直接执行的. NSOperation 对象的 isConCurrent 方法会告诉我们这个操作相对于调用 start 方法的线程,是同步还是异步. isConCurrent 方法默认返回 NO,表示操作与调用线程同步执行

小结:
队列名称的作用:将来调试的时候,可以看得出任务是在哪个队列中执行的

同步函数不具备开启线程的能力,无论什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)

0 0
原创粉丝点击