iOS多线程详细说明(GCD篇-就是个玩儿)
相信对于一些开发者,这个词并不陌生,而且面试中经常会被问到。开发过程中我们也会经常用到,单例、延时、遍历、异步操作...今天就来细说一下GCD是啥,是用来干啥的
GCD全称为Grand Central Dispatch,GCD是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接去操作线程。
进入正题之前,我们先来了解几个概念
- 1.进程: 一个具有一定独立功能的程序关于某个数据集合的一次 运行活动。可以理解成一个运行中的应用程序(比如我的妈妈在做饭)自己可以呼出任务管理器,里面 都是进程。
- 2.线程: 程序执行流的最小单元,线程是进程中的一个实体。通俗的理解就是 [妈妈在做饭,其中手在切菜(一个线程),耳朵在听音乐(另外一个线程)]
- 3.同步: 只能在当前线程按先后顺序依次执行,不开启新线程 [妈妈做饭:只能锅里先倒油(线程1),才能炒菜(线程2),不倒油是炒不了菜的]。
- 4.异步: 可以在当前线程开启多个新线程执行,可不按顺序执行
[妈妈在炒菜的时候(线程1),米饭是在煮的(线程2)]。 - 5.队列: 装载线程任务的队形结构。
- 6.并发: 线程执行可以同时一起进行执行(对应异步)。
- 7.串行: 线程执行只能依次逐一先后有序的执行(对应同步)。
GCD有几种Queue(队列)
-
1.****The main queue****(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行, Main queue 可以通过dispatch_get_main_queue()来获取。
-
2.****Global queue****(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。 Global queue 可以通过调用dispatch_get_global_queue函数来获取(可以设置优先级)
-
3.****Custom queue**** (自定义队列): 可以为串行,也可以为并发。Custom queue 可以通过dispatch_queue_create()来获取;
-
4.****Group queue ****(队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。
关于线程
dispatch_asyn和dispatch_sync添加任务到dispatch队列时,是否创建线程呢,那么创建线程是创建一个呢还是多个呢?
- 1.dispatch_sync添加任务到队列,不会创建新的线程都是在当前线程中处理的。无论添加到串行队列里或者并行队列里,都是串行效果,因为这个方法是等任务执行完成以后才会返回。
- 2.dispatch_async添加任务到
- 2.1:mainQueue不创建线程,在主线程中串行执行
- 2.2:globalQueue 和 并行队列:根据任务系统决定开辟线程个数
- 2.3:串行对列:创建一个线程:串行执行。
贴代码的时间段了
1.异步 主线程(一般用做刷新UI操作)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"123");
//异步 主线程 串行队列 这个相当于放在主线程的最后面去执行的 (FIFO)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"我是获取主队列执行的任务1 %@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"我是获取主队列执行的任务2 %@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"我是获取主队列执行的任务3 %@",[NSThread currentThread]);
});
NSLog(@"主线程的结尾 %@",[NSThread currentThread]);
sleep(2);//?< 卡住主线程2秒
}
打印结果:
123
主线程的结尾** <NSThread: 0x60800007d780>{number = 1, name = main}**
我是获取主队列执行的任务1 <NSThread: 0x60800007d780>{number = 1, name = main}
我是获取主队列执行的任务2 <NSThread: 0x60800007d780>{number = 1, name = main}
我是获取主队列执行的任务3 <NSThread: 0x60800007d780>{number = 1, name = main}
//关键字:**dispatch_async**---异步执行,也就是说block里面代码的执行,跟当前didload 里面的没关系 所有当碰到直接略过,由于是**dispatch_get_main_queue()** 所以当主线程结束的时候 3个任务才执行。(**主线程一般用来刷新UI操作,这个是由顺序的哟,为什么会有顺序呢,因为这些任务所在的队列是串行队列,在添加任务时候,是按照代码的顺序来的,所以在执行的时候要保证FIFO(先进先出)的原则来执行**)
###2.同步 主线程(死锁---禁忌)
//同步 主线程 串行 死锁
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"我是死锁的上面");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"我是死锁 线程:%@",[NSThread currentThread]);
});
NSLog(@"我是死锁的下面");
}
>打印结果:
我是死锁的上面
***为什么会出现这种情况呢 我给大家画一个图 就很好明白了***
![Paste_Image.png](http:https://img.haomeiwen.com/i4241428/8af55b01905d6087.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/200)
>**dispatch_get_main_queue()** 刚才已经介绍过了 ,是将任务放在最底下,也就是当主线程的该走的都走完了,没啥事了 才处理你的任务,**dispatch_sync** (同步),也就是说要等block里的任务执行完才能继续往下走,要不然就卡住
**看一下上图,整个程序要等block里面的执行完才能执行,而block的执行需要整个程序走完才能执行,两个东西互相都在等,那么永远都不能见面**
###2.异步 其它线程(一般的耗时操作:I/O操作,上传照片...)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"123");
//异步 子线程 并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"异步1 线程 :%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步2 线程 :%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"异步3 线程 :%@",[NSThread currentThread]);
});
NSLog(@"456");
NSLog(@"主线程即将结束 %@",[NSThread currentThread]);
}
>```
打印结果:
123
456
异步1 线程:<NSThread: 0x608000270740>{number = 6, name = (null)}
异步2 线程:<NSThread: 0x60000026e540>{number = 7, name = (null)}
主线程即将结束<NSThread: 0x60000007da00>{number = 1, name = main}
异步3 线程 :<NSThread: 0x608000270100>{number = 8, name = (null)}
这里面我让任务3卡当前线程2秒,我们可以看到,主线程依然往下走了,说明这个3个任务的执行完全跟主线程没有关系
Global Queue 的优先级
#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_queue_t globaleQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globaleQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globaleQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globaleQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(globaleQueue1, ^{
NSLog(@"我是任务1 %@",[NSThread currentThread]);
});
dispatch_async(globaleQueue3, ^{
NSLog(@"我是任务3 %@",[NSThread currentThread]);
});
dispatch_async(globaleQueue4, ^{
NSLog(@"我是任务4 %@",[NSThread currentThread]);
});
dispatch_async(globaleQueue2, ^{
NSLog(@"我是任务2 %@",[NSThread currentThread]);
});
我是任务1 <NSThread: 0x600000274dc0>{number = 6, name = (null)}
我是任务2 <NSThread: 0x600000274440>{number = 8, name = (null)}
我是任务3 <NSThread: 0x600000274080>{number = 7, name = (null)}
我是任务4 <NSThread: 0x60800026d480>{number = 9, name = (null)}
PS:异步发送的,但是无论我们运行多少次,看到的结果都是有顺序的,也就验证了优先级的事情
前3个我就不做过多的介绍了,看字面意思就大概清楚了,哪个queue的优先级最高,当然是high喽!主要介绍的就是这个DISPATCH_QUEUE_PRIORITY_BACKGROUND,我们先来看它后面那个是什么(INT16_MIN)
INT16_MIN
我们可以看到,苹果的源码里我们看的很清楚前 3 个优先级的级别分别是2、0、-2,也就是说2是最高的, -2是最低的. INT16_MIN是个什么鬼!?字面上理解,16位最小值( Minimum value of a signed 16-bit integer)-2^15(- 32768 ),至于为什么是- 32768 ,这个涉及到一点计算机基础(补码),自行查阅。
说了一堆废话 也就是说DISPATCH_QUEUE_PRIORITY_BACKGROUND优先级最低,那么有人就会问,我们什么时候会用到这个DISPATCH_QUEUE_PRIORITY_BACKGROUND呢?还要补充一点知识 Throttle 机制,这里不做过多介绍(因为我不知道,嘻嘻!)
DISPATCH_QUEUE_PRIORITY_BACKGROUND应用场景
对于重度磁盘I/O依赖的后台任务,如果对实时性要求不高,放到DISPATCH_QUEUE_PRIORITY_BACKGROUND Queue中是个好习惯,对系统更友好(别人的话)
也就是说,一些非常耗时的操作,但是啥时候执行完都成,那就放在最后喽,反正我不要立刻看到结果,这个时候就用DISPATCH_QUEUE_PRIORITY_BACKGROUND
3.Custom Queue
//子线程 串行队列
//DISPATCH_QUEUE_SERIAL 串行
//DISPATCH_QUEUE_CONCURRENT 并行
//DISPATCH_QUEUE_SERIAL_INACTIVE 串行不活跃的
//DISPATCH_QUEUE_CONCURRENT_INACTIVE 并行不活跃的
dispatch_queue_t customQueue = dispatch_queue_create("key", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(customQueue, ^{
//任务
NSLog(@"我是自定义任务 %@",[NSThread currentThread]);
});
这个东西如果要用的话,用前两个就足够了
4.Dispatch_Group(监听任务完成情况)
dispatch_group_t 主要跟 dispatch_group_notify来配合使用,当放在 dispatch_group_t 里面的队列和任务都完成以后,会响应dispatch_group_notify
dispatch_group_t group = dispatch_group_create(); //?< 创建一个组
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //?< 创建一个全局队列
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"任务1 %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"任务2 %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"任务3 %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"任务4 %@",[NSThread currentThread]);
});
//这一定要放在添加队列的后面
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"结束了 %@",[NSThread currentThread]);
});
打印结果:
任务1 <NSThread: 0x608000270000>{number = 6, name = (null)}
任务2 <NSThread: 0x600000275880>{number = 5, name = (null)}
任务3 <NSThread: 0x60800026fc40>{number = 4, name = (null)}
任务4 <NSThread: 0x60800026e540>{number = 3, name = (null)}
结束了 <NSThread: 0x608000270000>{number = 6, name = (null)}
5.Dispatch Semaphore(信号量)
dispatch_semaphore_create; //创建一个信号量
dispatch_semaphore_wait 让那个信号量-1
dispatch_semaphore_signal 让那个信号量+1
先说说信号量的工作原理,当我初始化一个信号量的时候,会给他一个初始值 **dispatch_semaphore_create(1) **,然后我们调用 dispatch_semaphore_wait(上面的信号量对象, DISPATCH_TIME_FOREVER) 会检测那个信号量的值是否大于 0 ,如果大于0就继续执行下面的代码,并把那个信号量的值-1,如果 小于等于0 那么当前线程就会卡到这一句,等待信号量的值增加并且大于 0
dispatch_semaphore_signal 就是用来增加信号量的值的,下面我们看一段代码
//1.创建信号量 并且设置为10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
//2.创建个queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{ // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"%i",i);
sleep(2);
// 每次发送信号则semaphore会+1,
dispatch_semaphore_signal(semaphore);
});
}
打印结果:
每两秒钟打印10次,就不贴了
6.Dispatch Barrier(类似信号量的东西[Barrier:障碍物])
dispatch_barrier_async 这个东西用作依赖关系,举个例子: (1,2,3任务)执行完 -->barrier任务 -->(4,5,6任务)
dispatch_queue_t barrierQueue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);//?< 自定义以个并发队列
dispatch_async(barrierQueue, ^{
NSLog(@"任务1");
});
dispatch_async(barrierQueue, ^{
NSLog(@"任务2");
});
dispatch_async(barrierQueue, ^{
NSLog(@"任务3");
});
// 会等待 队列里面之前的任务都完成以后在执行下面的
dispatch_barrier_async(barrierQueue, ^{
sleep(5);
NSLog(@"barrier任务");
});
NSLog(@"mark-----1");
dispatch_async(barrierQueue, ^{
NSLog(@"任务4");
});
NSLog(@"mark-----2");
dispatch_async(barrierQueue, ^{
NSLog(@"任务5");
});
dispatch_async(barrierQueue, ^{
NSLog(@"任务6");
});
打印结果:
2017-01-16 13:49:22.138 xxx[1690:159260] mark-----1
2017-01-16 13:49:22.138 xxx[1690:219423] 任务1
2017-01-16 13:49:22.138 xxx[1690:219424] 任务3
2017-01-16 13:49:22.138 xxx[1690:219414] 任务2
2017-01-16 13:49:22.139 xxx[1690:159260] mark-----2
(这之间延迟5秒)
2017-01-16 13:49:27.141 xxx[1690:219414] barrier任务
2017-01-16 13:49:27.142 xxx[1690:219414] 任务4
2017-01-16 13:49:27.142 xxx[1690:219424] 任务5
2017-01-16 13:49:27.142 xxx[1690:219423] 任务6
>我们很清楚的可以看到 barrier任务是在 任务1、2、3完成以后执行的,也就是在barrier任务执行的时候 4、5、6的任务是没有执行的
>这里有一点值得注意的是 我们的 **barrier任务** 是 **异步** 发送的,如果改成 dispatch_barrier_sync(同步)会是什么样子呢? 我们直接看下打印出来的结果吧
>```
打印结果:
2017-01-16 13:56:46.256 xxx[1772:226370] 任务1
2017-01-16 13:56:46.256 xxx[1772:226346] 任务2
2017-01-16 13:56:46.256 xxx[1772:226347] 任务3
(这之间延迟5秒)
2017-01-16 13:56:51.258 xxx[1772:226302] barrier任务
2017-01-16 13:56:51.259 xxx[1772:226302] mark-----1
2017-01-16 13:56:51.259 xxx[1772:226302] mark-----2
2017-01-16 13:56:51.259 xxx[1772:226347] 任务4
2017-01-16 13:56:51.260 xxx[1772:226346] 任务5
2017-01-16 13:56:51.260 xxx[1772:226370] 任务6
这里我们注意的是mark------1 和 mark------2的位置,也就是说 dispatch_barrier_async 会卡住线程因为是同步发送的。
最前面我们提到的死锁又来了
既然dispatch_barrier_async会卡住线程,那么如果所插入的队列是 The main queue(主线程串行队列)
Paste_Image.png打印结果:
2017-01-16 14:05:47.324 xxx[1810:234790] 我是死锁的上面
#7.Dispatch Apply
>dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
dispatch_queue_t applyQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(applyQueue, ^{
NSLog(@"内部执行的任务1 线程%@",[NSThread currentThread]);
sleep(2);
NSLog(@"内部执行的任务2 线程%@",[NSThread currentThread]);
});
dispatch_apply(10, applyQueue, ^(size_t index) {
NSLog(@"我是插入的第%zu次 %@",index,[NSThread currentThread]);
});
2017-01-16 14:11:38.075 xxx[1827:240047] 我是插入的第0次 <NSThread: 0x600000075280>{number = 1, name = main}
2017-01-16 14:11:38.075 xxx[1827:240081] 内部执行的任务1 线程<NSThread: 0x608000263c40>{number = 3, name = (null)}
2017-01-16 14:11:38.075 xxx[1827:240083] 我是插入的第1次 <NSThread: 0x6080002627c0>{number = 4, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240214] 我是插入的第2次 <NSThread: 0x608000262680>{number = 5, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240215] 我是插入的第3次 <NSThread: 0x608000264180>{number = 6, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240047] 我是插入的第4次 <NSThread: 0x600000075280>{number = 1, name = main}
2017-01-16 14:11:38.076 xxx[1827:240083] 我是插入的第5次 <NSThread: 0x6080002627c0>{number = 4, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240214] 我是插入的第6次 <NSThread: 0x608000262680>{number = 5, name = (null)}
2017-01-16 14:11:38.077 xxx[1827:240047] 我是插入的第8次 <NSThread: 0x600000075280>{number = 1, name = main}
2017-01-16 14:11:38.076 xxx[1827:240215] 我是插入的第7次 <NSThread: 0x608000264180>{number = 6, name = (null)}
2017-01-16 14:11:38.077 xxx[1827:240083] 我是插入的第9次 <NSThread: 0x6080002627c0>{number = 4, name = (null)}
2017-01-16 14:11:40.078 xxx[1827:240081] 内部执行的任务2 线程<NSThread: 0x608000263c40>{number = 3, name = (null)}
>这个单纯的插入任务。。
##好了,累了!容我抽支烟