前言
上篇文章,对多线程的几种形式做了探索,也提到了GCD的使用,那么这篇文章将对GCD进行重点探索。
资源准备
GCD的简介
-
GCD --- 全称是
Grand Central Dispatch; -
纯
C语言,提供例如非常强大的函数; -
GCD作用:将任务添加到队列,并指定任务执行的函数。
GCD的优势
-
GCD是苹果公司为多核的并行运算提出的解决方案; -
GCD会自动利用更多的CPU内核(比如双核、四核); -
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程); -
程序员只需要告诉
GCD想要执行什么任务,不需要编写任何线程管理代码。
GCD的基础
其基本用法,代码如下:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
还原最基础的写法,可拆分成任务、队列和函数:
-
使用
dispatch_block_t创建任务:-
任务使用
block封装; -
任务的
block没有参数,也没有返回值;
-
-
使用
dispatch_queue_t创建队列; -
将任务添加到队列,并指定执行任务的函数
dispatch_async。
下面用代码演示下:
- (void)syncTest{
// 任务
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
// 函数
dispatch_async(queue, block);
}
函数
在GCD中执行任务的方式有两种,同步执行和异步执行:
-
dispatch_sync:同步函数 -
dispatch_async:异步函数
dispatch_sync
同步函数dispatch_sync的特性:
-
必须等待当前语句执行完毕,才会执行下一条语句;
-
不会开启线程,即不具备开启新线程的能力;
-
在当前线程中执行
block任务;
dispatch_async
异步函数dispatch_async的特性:
-
不用等待当前语句执行完毕,就可以执行下一条语句;
-
会开启线程执行
block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关); -
异步是多线程的代名词;
二者的区别
-
是否等待队列的任务执行完毕;
-
是否具备开启新线程的能力。
队列
队列分为串行队列和并发队列,用来存放任务。队列是一种数据结构,属于特殊的线性表,遵循先进先出(FIFC)原则。新任务被插入到队尾,而任务的读取从队首开始。每读取一个任务,则队列中释放一个任务;
在GCD中,还提供了两个特殊的队列,分别是主队列和全局并发队列。主队列属于串行队列,而全局并发队列属于并发队列;
队列和线程并没有关系,队列负责任务的调度,任务的执行依赖于线程,优先调度的任务不一定优先执行;
串行队列
串行队列:Serial Dispatch Queue:
-
每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个;
-
只开启一个线程,同一时刻只调度一个任务执行;
-
使用
DISPATCH_QUEUE_SERIAL创建串行队列; -
DISPATCH_QUEUE_SERIAL也可传入NULL,默认创建为串行队列。
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lg.serial", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lg.serial", DISPATCH_QUEUE_SERIAL);
并发队列
并发队列:Concurrent Dispatch Queue:
-
一次可以并发执行多个任务;
-
开启多个线程,同一时刻可以调度多个任务执行;
-
使用
DISPATCH_QUEUE_CONCURRENT创建并发队列; -
并发队列的并发功能只有在异步函数下才有效。
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.concurrent", DISPATCH_QUEUE_CONCURRENT);
主队列
主队列:Main Dispatch Queue:
-
主队列:
GCD提供的特殊的串行队列; -
专门用来在主线程上调度任务的串行队列,依赖于主线程、主
Runloop,在main函数调用之前自动创建; -
不会开启线程;
-
如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度;
-
使用
dispatch_get_main_queue获得主队列; -
通常在返回主线程更新
UI时使用。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
全局并发队列
全局并发队列:Global Dispatch Queue:
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
-
GCD提供的默认的并发队列; -
为了方便开发者使用,苹果提供了全局队列;
-
在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列;
-
使用
dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0):-
参数1表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT = 0,被服务质量quality of service取代; -
参数2为标记,是为了未来使用保留的。所以这个参数应该永远指定为0;
-
优先级从高到低,对应服务质量:
-
DISPATCH_QUEUE_PRIORITY_HIGH:QOS_CLASS_USER_INITIATED -
DISPATCH_QUEUE_PRIORITY_DEFAULT:QOS_CLASS_DEFAULT -
DISPATCH_QUEUE_PRIORITY_LOW:QOS_CLASS_UTILITY -
DISPATCH_QUEUE_PRIORITY_BACKGROUND:QOS_CLASS_BACKGROUND
日常开发中,主队列 + 全局并发队列的使用:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//执行耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
//回到主线程进行UI操作
});
});
队列与函数的搭配使用
串行队列和函数的搭配
同步函数
- 不会开启线程,在当前线程执行;
- 任务串行执行,一个接一个。 代码验证:
/**
串行同步队列 : FIFO: 先进先出
*/
- (void)serialSyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<5; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
异步函数
- 开启新线程;
- 任务串行执行,一个接一个; 代码验证:
/**
串行异步队列
*/
- (void)serialAsyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 5; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
并发队列和函数的搭配
同步函数
- 不会开启线程,在当前线程执行;
- 任务串行执行,一个接一个。 代码验证:
/**
同步并发 : 堵塞 同步锁 队列 : resume supend 线程 操作, 队列挂起 任务能否执行
*/
- (void)concurrentSyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 5; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
异步函数
- 开启新线程;
- 任务异步执行,没有顺序,和
CPU调度有关。 代码验证:
/**
异步并发: 有了异步函数不一定开辟线程
*/
- (void)concurrentAsyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<100; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
主队列和函数的搭配
同步函数
-
主队列的作用,专门用来在主线程上调度任务的串行队列;
-
主队列中增加同步函数,导致主线程需要等待同步函数完成后再执行;
-
由于主队列是特殊的串行队列,同步函数需要等待主线程完成后再执行;
-
所以,两个任务相互等待,产生死锁。 代码验证: 会直接崩溃:
异步函数
- 不会开启线程,在当前线程执行;
- 任务串行执行,一个接一个。 代码验证:
/**
主队列异步
不会开线程 顺序
*/
- (void)mainAsyncTest{
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i<5; i++) {
dispatch_async(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
全局队列和函数的搭配
同步函数
- 不会开启线程,在当前线程执行;
- 任务串行执行,一个接一个。 代码验证:
/**
全局同步
全局队列:一个并发队列
*/
- (void)globalSyncTest{
for (int i = 0; i<5; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
异步函数
- 开启新线程;
- 任务异步执行,没有顺序,和
CPU调度有关。 代码验证:
/**
全局异步
全局队列:一个并发队列
*/
- (void)globalAsyncTest{
for (int i = 0; i<20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
打印结果:
线程死锁
所谓线程死锁,是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
死锁会导致奔溃:
再查看堆栈信息,里面就会有 _dispatch_sync_f_slow 的调用,即为死锁的异常
面试题解析
面试题 1
- (void)wbinterDemo {
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"顺序1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"顺序2:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"顺序3:%@",[NSThread currentThread]);
});
NSLog(@"顺序4:%@",[NSThread currentThread]);
});
NSLog(@"顺序5:%@",[NSThread currentThread]);
}
分析:首先知道是并发队列 DISPATCH_QUEUE_CONCURRENT,最先执行顺序1的打印,接着往下走,异步线程不会阻塞线程,执行到异步线程里面,但是异步线程返回的信息不确定,也不用等待,所以直接往下执行,打印顺序5,剩下的就是异步线程里面的内容了,在异步线程里面没有排序操作,所以就按顺序直接打印顺序2、顺序4,里面还有个异步线程,所以最后打印顺序3。
查看打印结果:
面试题2
- (void)wbinterDemo1{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"顺序1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"顺序2:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"顺序3:%@",[NSThread currentThread]);
});
NSLog(@"顺序4:%@",[NSThread currentThread]);
});
NSLog(@"顺序5:%@",[NSThread currentThread]);
}
分析:首先知道是并发队列 DISPATCH_QUEUE_CONCURRENT,最先执行顺序1的打印,接着往下走,异步线程不会阻塞线程,执行到异步线程里面,但是异步线程返回的信息不确定,也不用等待,所以直接往下执行,打印顺序5,剩下的就是异步线程里面的内容了,在异步线程里面没有排序操作,但是里面还有个同步线程,串行执行,所以就按顺序直接打印顺序2、顺序3、顺序4。
打印结果:
面试题3
- (void)wbinterDemo4 {
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
NSLog(@"顺序1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"顺序2:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"顺序3:%@",[NSThread currentThread]);
});
NSLog(@"顺序4:%@",[NSThread currentThread]);
});
NSLog(@"顺序5:%@",[NSThread currentThread]);
}
分析:首先确定是串行队列,顺序1 和顺序5在主队列,不受影响,异步函数先执行顺序2,但是顺序4要等待同步函数优先执行;由于使用串行队列,同步函数又需要异步函数执行完成后才能执行,所以,两个函数相互等待,产生死锁。
打印结果:
面试题4
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"顺序1");
});
dispatch_async(queue, ^{
NSLog(@"顺序2");
});
dispatch_sync(queue, ^{ NSLog(@"顺序3"); });
NSLog(@"顺序0");
dispatch_async(queue, ^{
NSLog(@"顺序7");
});
dispatch_async(queue, ^{
NSLog(@"顺序8");
});
dispatch_async(queue, ^{
NSLog(@"顺序9");
});
}
有4个选项:
- A: 1230789
- B: 1237890
- C: 3120798
- D: 2137890
正确答案:
A、C
首先确定是串行队列,执行顺序1和顺序2在异步函数中,所以顺序不确定;顺序3在同步函数中,顺序0在主线程,所以顺序3一定在顺序0的前面;
执行的顺序7、顺序8、顺序9一定会在顺序3和顺序0之后,但它们在异步函数中,所以顺序也是不确定的。
题目5
队列的类型有几种?
正确答案:两种
-
串行队列:
serialQueue、mainQueue -
并发队列:
concurrentQueue、globalQueue
//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.lg.cn", NULL);
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
题目 6
@property (atomic, assign) int num;
- (void)MTDemo {
while (self.num < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num++;
});
}
NSLog(@"end : %d",self.num);
}
- 正确答案:
>= 5, - 首先一点
num的值不能小于5,因为小于5的时候,是出不来while循环的。 - 上面添加的都是异步函数,这就导致
num值为5的时候,可能还有任务在执行self.num++操作,所以num的值有可能大于5。
面试题 7
for (int i= 0; i<10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num++;
});
}
NSLog(@"end : %d",self.num);
}
- 这个面试题和上面的有所不同,这里的条件判断和自增不是用的同一个变量,代码会循环
10000次不停的添加self.num++任务,这就导致我们执行++操作时,可能num的值已经变大了,但是我们还是加的之前的小值,所以最终会<= 10000。