主要内容:
1、队列的认识
2、任务的认识
3、队列和任务搭配使用的面试题
4、死锁的认识和解决
队列
队列就是管理待执行任务的等待队列,用来调度任务给线程执行,符合先进先出原则
并发和串行决定了任务执行的顺序,并发是多个任务并发执行,串行是指任务顺序执行,也就是一个任务执行完成再执行下一个任务
- 队列负责调度任务,提交任务给线程执行
- 队列遵循先进先出原则,在这里指的是先进先调度。而不是先调度完成。
- 队列底层会维护一个线程池来处理用户提交的任务,线程池的作用就是执行队列管理的任务。
- 串行队列底层只维护了一个线程,并发队列的底层维护了多个线程
- 串行队列一次只能处理一个任务,上一个任务处理完成才能处理下一个任务
- 并发队列可以同时处理多个任务,虽然处理顺序还是先进先出的原则,但是有的任务处理时间比较长,有可能先进后调度完成。
- 队列是先进先调度,如果是串行队列是先进先调度结束,并发队列并不是先进先调度结束,可以同时调度多个任务
创建队列:
- (void)createQueueTest{
//创建队列
dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("串行队列", NULL);
//获取已有队列
//获取主队列
dispatch_queue_t queue4 = dispatch_get_main_queue();
//获取全局并发队列
/*
第一个参数是优先级,第二个无意义,填0
*/
dispatch_queue_t queue5 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
}
任务
任务就是线程要执行的那段代码
执行任务有两种方式,同步和异步,同步和可以异步决定是否要开启新的线程,同步不开启,异步开启。
同步函数(sync):
- 同步函数用来实现线程同步执行任务
- 只能在当前线程中执行任务,不具备开启新线程的能力
- 同步函数只能等block执行完成后才能返回
异步函数(async):
- 异步函数用来实现线程异步执行任务
- 异步函数不需要等block执行完成后就可以返回
- 可以开启新线程
提交任务的函数很多都有两个版本,一个是传两个参数,传递作为任务的block代码,一个是传三个参数,传递作为任务的方法。
- (void)createTaskTest{
/*
参数一:队列
参数二:作为任务的block代码
*/
//在调度队列上提交异步执行的块并立即返回
dispatch_async(dispatch_queue_t _Nonnull queue, <#^(void)block#>);
/*
参数一:队列
参数二:上下文
参数三:作为任务的函数
*/
dispatch_async_f(<#dispatch_queue_t _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>);
/*
参数一:队列
参数二:作为任务的block代码
*/
//提交块对象以执行,并在该块完成执行后返回。
dispatch_sync(dispatch_queue_t _Nonnull queue, <#^(void)block#>);
/*
参数一:队列
参数二:上下文
参数三:作为任务的函数
*/
dispatch_sync_f(<#dispatch_queue_t _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>);
}
注意:
- 任务是线程执行的,不是队列执行,队列只是用来调度任务的
- 任务的执行方式有同步和异步,同步不开辟新线程,异步会开辟新线程
主队列
主队列是一种特殊的串行队列,特殊在于只使用在主线程和主Runloop中,并且是在启动APP时自动创建的。
主队列是指主线程的队列,是一个串行队列,在主线程去执行其实就是放入了主队列
全局并发队列
全局并发队列是系统提供的,开发者可以直接使用的一个并发队列,没有其他的特殊之处。
在获取时可以给定优先级
- 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
总结
任务队列线程的关系.png
- 线程有两种方式执行任务,同步和异步,分别通过同步函数和异步函数来实现
- 同步函数添加的任务,该线程执行完这个任务才可以执行其他任务,异步函数添加的任务,线程可以并发执行该任务
- 任务存放在队列中,队列有两种调度方式,串行和并发,串行只能顺序处理任务,并发可以同时处理多个任务
- 线程想要并发执行任务,需要异步函数添加任务给并发队列
- 如果队列是串行的,即使线程可以并发执行任务也不行,因为队列是串行调度给线程的。
- 同步函数需要等待block执行完成才可以返回
- 异步函数不需要等待block执行完成就可以返回
队列和任务的搭配使用
任务和队列以及线程的执行关系.png说明:
- 同步不开启新线程,异步会开启新线程
- 并发队列可以并发调度任务,串行队列只能顺序调度任务
- 只有并发队列提交给线程异步执行的任务才可以异步执行
总结:
队列和任务的常见类型代码演示
1、基础写法
给一个串行队列添加了一个任务,该任务需要异步执行
- (void)syncTest{
// 把任务添加到队列 --> 函数
// 任务 _t ref c对象
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
//串行队列
dispatch_queue_t queue = dispatch_queue_create("wy", NULL);
// 函数
dispatch_async(queue, block);
}
2、主队列同步
- (void)mainSyncTest{
NSLog(@"0");
// 等
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
执行结果: 0,之后死锁
死锁.png说明:
- 在主队列中添加了三个任务,任务0,同步任务块,任务2
- 同步任务块的任务本身就是给主队列添加了任务1
- 因此现在主队列的任务有四个,并且顺序为任务0、同步任务块、任务2、任务1,这几个任务依次执行
- 同步任务块需要等待任务1的完成才能返回,但是任务1的完成又在同步任务块的后面
- 所以造成了同步任务块和任务1的相互等待,死锁导致程序崩溃
注意:造成死锁是同步任务块和任务1的相互等待,与任务2没关系,就算删掉任务2,也会死锁
3、 主队列异步
- (void)mainAsyncTest{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
执行结果: 21
说明:
- 主队列中添加两个任务,异步任务块和任务2
- 异步任务块的任务本身是给主队列添加了一个任务1
- 这样主队列有三个任务,并且顺序为异步任务块、任务2、任务1
- 因为任务1是异步提交的,也就是线程以异步的方式来执行,异步任务块不需要等block执行完成就可以返回
- 因此这里不会发生死锁
4、 全局并发队列异步
并发执行任务
- (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");
}
结果:
2021-10-31 14:58:16.038522+0800 多线程使用[11062:5684222] hello queue
2021-10-31 14:58:16.038559+0800 多线程使用[11062:5684360] 0-<NSThread: 0x600003d438c0>{number = 6, name = (null)}
2021-10-31 14:58:16.038564+0800 多线程使用[11062:5684368] 2-<NSThread: 0x600003d34440>{number = 7, name = (null)}
2021-10-31 14:58:16.038567+0800 多线程使用[11062:5684359] 3-<NSThread: 0x600003d28780>{number = 4, name = (null)}
2021-10-31 14:58:16.038571+0800 多线程使用[11062:5684358] 1-<NSThread: 0x600003d282c0>{number = 8, name = (null)}
2021-10-31 14:58:16.038585+0800 多线程使用[11062:5684361] 4-<NSThread: 0x600003d31b80>{number = 3, name = (null)}
2021-10-31 14:58:16.038606+0800 多线程使用[11062:5684362] 5-<NSThread: 0x600003d43e00>{number = 5, name = (null)}
2021-10-31 14:58:16.038642+0800 多线程使用[11062:5684360] 7-<NSThread: 0x600003d438c0>{number = 6, name = (null)}
说明:
- 异步执行会开启新线程,因此有多个线程来执行
- 在并发队列中异步执行代码块,因此执行顺序是异步的
- 并且打印hello是最早执行的,这是因为异步并发执行,异步添加任务消耗的时间比较多,所以先执行hello
5、 全局并发队列同步
串行执行任务
- (void)globalSyncTest{
for (int i = 0; i<20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
结果:
2021-10-31 14:55:53.552472+0800 多线程使用[10971:5681912] 13-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552541+0800 多线程使用[10971:5681912] 14-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552635+0800 多线程使用[10971:5681912] 15-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552729+0800 多线程使用[10971:5681912] 16-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552829+0800 多线程使用[10971:5681912] 17-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559220+0800 多线程使用[10971:5681912] 18-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559332+0800 多线程使用[10971:5681912] 19-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559401+0800 多线程使用[10971:5681912] hello queue
说明:
- 主队列中有同步任务块和hello两个任务,同步任务块添加了多个任务到并发队列中
- 因为是同步函数,所以不会开启新线程,只有一个主线程来执行
- 并且打印hello是在最后执行的,这是因为同步代码块是在hello任务的前面,同步函数需要等代码块执行完成后再返回,所以需要等同步代码块执行完才能执行hello
简单面试题分析
面试题1
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
结果: 15243
说明:
- 在主队列中包含三个任务,同步任务1、异步任务代码块、同步任务5
- 异步任务块不需要等待函数执行完成就可以返回,而异步函数本身是耗时的,所以前面的顺序是1、5
- 在异步任务中将三个任务添加到一个并发队列中,包括任务2,同步代码块、任务4
- 因为是并发队列异步提交任务,所以这三个任务并发执行,
- 但因为队列的先进先出原则,先进的先调用,再看消耗的时间,同步代码块会消耗更多的时间,所以最后执行
- 任务2和任务4执行时间一样,但是任务2先执行
- 所以顺序为2、4、3,合起来就是15243
面试题2
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
结果: 1、5、2,崩溃
20-面试题解析2.png说明:
- 在主队列中包含三个任务,同步任务1、异步任务代码块、同步任务5
- 主队列是串行的,所以调度顺序是任务1、代码块、任务5
- 异步任务块不需要等block执行完就可以返回,所以顺序是1、5
- 代码块中将三个任务添加到一个并发队列中,包括任务2,同步代码块、任务4
- 在串行队列中,包含任务2和同步任务块、任务4、任务3,而同步任务块的执行结束需要等待任务3的结束
- 造成死锁,因此在执行完3后会崩溃
注意:
这里虽然是主队列,任务块在任务5的前面,但是在执行时我们看到先执行了任务5后执行了任务2,
这是因为异步操作并不需要等待函数的执行完成而完成,
面试题3
- (void)wbinterDemo{
dispatch_queue_t queue11 = dispatch_queue_create("wy", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue11, ^{
NSLog(@"1");
});
dispatch_async(queue11, ^{
NSLog(@"2");
});
dispatch_sync(queue11, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue11, ^{
NSLog(@"1");
});
dispatch_async(queue11, ^{
NSLog(@"7");
});
dispatch_async(queue11, ^{
NSLog(@"8");
});
dispatch_async(queue11, ^{
NSLog(@"9");
});
}
请选择:
A:1230789
B:1237890
C:3120798
D:2137890
答案为AC
说明:
- 首先这些任务块操作都是在主队列中,其中任务3不会开启新线程,和任务0都是主线程执行的,其他的任务在并发队列中,会开启新线程执行,同步任务块必须等任务3执行完才能继续往下执行,所以任务3肯定在任务0前面。(简单认识,同步并发是串行,所以任务3在任务0前面)
- 7、8、9任务在任务0后添加,所以会在0任务后面执行
- 同时7、8、9任务都是异步并发,所以并发执行,没有顺序
- 任务1、2在任务3之前添加到并发队列中,也就是任务1和2与3在不同的队列中,而任务1和2又是异步并发的,所以他们三个会异步并发执行,他们三个是没有顺序的。
死锁的认识
死锁就是同一个串行队列中两个任务相互等待引起死锁.
网上面很多说是不同的线程相互等待引起的,是解释不通的,应该是队列,因为队列是先进先出原则导致,而后面进入的任务的结束又依赖于前面进的任务。
一个串行队列的线程池只维护了一个线程。
死锁的场景
主队列同步执行
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务1");
});
}
说明:
- 在主队列中有同步任务块,之后添加任务1到主队列中。
- 而同步任务块的执行完成需要等block执行完成,所以同步任务块的执行依赖于任务1
- 而在串行队列中,任务1的完成又需要同步任务块的完成。
- 由此同步任务块和任务1相互等待,造成了死锁
同步串行队列的嵌套场景也是一样的,不再多言
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
死锁的解决
上面知道死锁是一个串行队列中有两个任务相互等待导致的,所以有两个解决办法,
- 队列为并发,不是串行
- 异步任务,取消一方的等待,也就打破相互等待
所以可以有两个做法,
将队列改为并发
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
将任务改为异步任务,及不会相互等待
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
网友评论