一、GCD队列
1、什么是GCD队列呢?我们可以从开发文档中得到以下解释:
A dispatch queue invokes blocks submitted to it serially in FIFO order. A serial queue invokes only one block at a time, but independent queues may each invoke their blocks concurrently with respect to each other.
The global concurrent queues invoke blocks in FIFO order but do not wait for their completion, allowing multiple blocks to be invoked concurrently.
The system manages a pool of threads that process dispatch queues and invoke blocks submitted to them. Conceptually, a dispatch queue may have its own thread of execution, and interaction between queues is highly asynchronous.
Dispatch queues are reference counted via calls to dispatch_retain and dispatch_release. Pending blocks submitted to a queue also hold a reference to the queue until they have finished. Once all references to a queue have been released, the queue will be deallocated by the system.
翻译后的意思大概就是:
GCD队列以FIFO顺序串行调用提交给它的块。 串行队列一次只调用一个块,但独立队列可以相互同时调用它们的块。
GCD全局队列以FIFO顺序调用块,但可以不等待它们完成,允许同时调用多个块。
系统管理一个线程池,用于处理GCD队列并调用提交给它们处理的块。 从概念上讲,调度队列可以有自己的执行线程,队列之间的交互是高度异步的。
GCD队列是通过调用dispatch_retain和dispatch_release来引用计数的。 提交到GCD队列的待处理块也会保留对队列的引用,直到它们完成为止。 一旦释放了对队列的所有引用,系统将释放队是通过调用dispatch_retain和dispatch_release来引用计数的。 提交到GCD队列的待处理块也会保留对队列的引用,直到它们完成为止。 一旦释放了对队列的所有引用,系统将释放队tiGCD列。
由上面可以知道,GCD队列其实也是一个对象,用来管理和调度线程执行提交给它的代码块。而队列可以分为两种模式,一种为串行队列,调用代码块的顺序为FIFO(先进先出)。另外一种为并发队列,可以进行并发调用代码块,即在先调用的代码块还没有执行完毕时,开始执行后续的代码块。而队列的释放已否则取决于被其他对象引用的情况,添加到GCD队列中还没执行的代码块,是否执行完毕也是个影响因素。
2、创建队列
创建队列主要通过dispatch_queue_t dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr);
label:队列名字
attr:队列类型
//串行队列
dispatch_queue_t que = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t que = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
3、系统自带队列
iOS中有两个自带队列,不需要主动创建,需要时直接获取即可。一个是:
主队列,是一个串行队列和主线程相关联,提交在主队列的代码块,将在主线程执行。
全局队列,是一个并发队列,提交代码块后,系统将自动帮忙分配线程进行执行。获取队列时候可以通过设置优先级,来区分代码块的执行优先级。
//获取主队列
dispatch_get_main_queue();
//获取全局队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
4、任务的执行
GCD任务执行分为两种:
- 同步执行:在当前线程添加任务,只有在之前的任务完成后,才会执行下一个任务。执行任务期间线程将被堵塞直到所有任务执行完毕
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
- 异步执行:将任务异步添加到线程中,不需要等待,就可以执行。且具有开启新线程的能力
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
二、队列任务类型与线程的关系
队列类型和执行任务类型可组成以下组合,让我们看看不同组合和线程间的关系
注:以下代码无特殊标注都是在主线程运行
- 串行队列+同步执行
eg:
NSLog(@"start");
dispatch_queue_t que = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(que, ^{
NSLog(@"1%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"1-%d",i);
}
});
dispatch_sync(que, ^{
NSLog(@"2%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"2-%d",i);
}
});
dispatch_sync(que, ^{
NSLog(@"3%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"3-%d",i);
}
});
NSLog(@"end");
运行结果为:
2019-07-19 15:44:20.476586+0800 OCTest[87488:18458095] start
2019-07-19 15:44:20.486586+0800 OCTest[87488:18458095] 1<NSThread: 0x102803480>{number = 1, name = main}
2019-07-19 15:44:20.486849+0800 OCTest[87488:18458095] 1-0
2019-07-19 15:44:20.486866+0800 OCTest[87488:18458095] 1-1
2019-07-19 15:44:20.486879+0800 OCTest[87488:18458095] 1-2
…
2019-07-19 15:44:20.534705+0800 OCTest[87488:18458095] 1-999
2019-07-19 15:44:20.534802+0800 OCTest[87488:18458095] 2<NSThread: 0x102803480>{number = 1, name = main}
2019-07-19 15:44:20.534868+0800 OCTest[87488:18458095] 2-0
2019-07-19 15:44:20.534919+0800 OCTest[87488:18458095] 2-1
2019-07-19 15:44:20.534933+0800 OCTest[87488:18458095] 2-2
…
2019-07-19 15:44:20.554682+0800 OCTest[87488:18458095] 2-999
2019-07-19 15:44:20.554725+0800 OCTest[87488:18458095] 3<NSThread: 0x102803480>{number = 1, name = main}
2019-07-19 15:44:20.554809+0800 OCTest[87488:18458095] 3-0
2019-07-19 15:44:20.554845+0800 OCTest[87488:18458095] 3-1
2019-07-19 15:44:20.554860+0800 OCTest[87488:18458095] 3-2
2019-07-19 15:44:20.554871+0800 OCTest[87488:18458095] 3-3
…
2019-07-19 15:44:20.575781+0800 OCTest[87488:18458095] 3-999
2019-07-19 15:44:20.585781+0800 OCTest[87488:18458095] end
由运行结果可以看出:
- 代码块始终在主线程执行,不曾新开线程
- 代码块的执行顺序也和我们添加顺序一致,同步执行。
- 执行块代码都在start-end中线程阻塞,等待块代码执行完毕
- 串行队列+异步执行
eg:
NSLog(@"start");
dispatch_queue_t que = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(que, ^{
NSLog(@"1%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"1-%d",i);
}
});
dispatch_async(que, ^{
NSLog(@"2%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"2-%d",i);
}
});
dispatch_async(que, ^{
NSLog(@"3%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"3-%d",i);
}
});
NSLog(@"end");
运行结果:
2019-07-19 15:55:20.567425+0800 OCTest[1633:18548596] start
2019-07-19 15:55:20.568241+0800 OCTest[1633:18548596] end
2019-07-19 15:55:20.475902+0800 OCTest[87586:18464637] 1<NSThread: 0x10180da70>{number = 2, name = (null)}
2019-07-19 15:55:20.476199+0800 OCTest[87586:18464637] 1-0
2019-07-19 15:55:20.476214+0800 OCTest[87586:18464637] 1-1
2019-07-19 15:55:20.476224+0800 OCTest[87586:18464637] 1-2
…
2019-07-19 15:55:20.517999+0800 OCTest[87586:18464637] 1-999
2019-07-19 15:55:20.518166+0800 OCTest[87586:18464637] 2<NSThread: 0x10180da70>{number = 2, name = (null)}
2019-07-19 15:55:20.518200+0800 OCTest[87586:18464637] 2-0
2019-07-19 15:55:20.518214+0800 OCTest[87586:18464637] 2-1
2019-07-19 15:55:20.518226+0800 OCTest[87586:18464637] 2-2
…
2019-07-19 15:55:20.536462+0800 OCTest[87586:18464637] 2-999
2019-07-19 15:55:20.536511+0800 OCTest[87586:18464637] 3<NSThread: 0x10180da70>{number = 2, name = (null)}
2019-07-19 15:55:20.536527+0800 OCTest[87586:18464637] 3-0
2019-07-19 15:55:20.536539+0800 OCTest[87586:18464637] 3-1
2019-07-19 15:55:20.536551+0800 OCTest[87586:18464637] 3-2
…
2019-07-19 15:55:20.554180+0800 OCTest[87586:18464637] 3-999
由运行结果可以看出:
- 执行过程中新开了一个线程
- 代码块的执行顺序也和我们添加顺序一致。
- 执行块代码过程中线程不会被阻塞
- 并发队列+同步执行
NSLog(@"start");
dispatch_queue_t que = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(que, ^{
NSLog(@"1%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"1-%d",i);
}
});
dispatch_sync(que, ^{
NSLog(@"2%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"2-%d",i);
}
});
dispatch_sync(que, ^{
NSLog(@"3%@",[NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"3-%d",i);
}
});
NSLog(@"end");
运行结果:
OCTest[88175:18495691] 2019-07-19 17:07:06.537673+0800 start
OCTest[88175:18495691] 1<NSThread: 0x10060a1f0>{number = 1, name = main}
2019-07-19 17:07:06.637673+0800 OCTest[88175:18495691] 1-0
2019-07-19 17:07:06.637701+0800 OCTest[88175:18495691] 1-1
2019-07-19 17:07:06.637766+0800 OCTest[88175:18495691] 1-2
…
2019-07-19 17:07:06.691119+0800 OCTest[88175:18495691] 1-999
2019-07-19 17:07:06.691223+0800 OCTest[88175:18495691] 2<NSThread: 0x10060a1f0>{number = 1, name = main}
2019-07-19 17:07:06.691242+0800 OCTest[88175:18495691] 2-0
2019-07-19 17:07:06.691254+0800 OCTest[88175:18495691] 2-1
2019-07-19 17:07:06.691266+0800 OCTest[88175:18495691] 2-2
…
2019-07-19 17:07:06.713741+0800 OCTest[88175:18495691] 2-999
2019-07-19 17:07:06.713790+0800 OCTest[88175:18495691] 3<NSThread: 0x10060a1f0>{number = 1, name = main}
2019-07-19 17:07:06.713807+0800 OCTest[88175:18495691] 3-0
2019-07-19 17:07:06.713818+0800 OCTest[88175:18495691] 3-1
2019-07-19 17:07:06.713836+0800 OCTest[88175:18495691] 3-2
…
2019-07-19 17:07:06.733478+0800 OCTest[88175:18495691] 3-999
OCTest[88175:18495691] 2019-07-19 17:07:06.737673+0800 end
由运行结果可以看出:
- 代码块始终在主线程执行,没有开启新线程
- 代码块的执行顺序和我们添加顺序一致
- 执行块代码过程中线程被阻塞,等待执行完添加的块代码
- 并发队列+异步执行
NSLog(@"start");
dispatch_queue_t que = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(que, ^{
NSLog(@"1%@",[NSThread currentThread]);
sleep(1);
NSLog(@"1-1%@",[NSThread currentThread]);
});
dispatch_async(que, ^{
NSLog(@"2%@",[NSThread currentThread]);
sleep(1);
NSLog(@"2-1%@",[NSThread currentThread]);
});
dispatch_async(que, ^{
NSLog(@"3%@",[NSThread currentThread]);
sleep(1);
NSLog(@"3-1%@",[NSThread currentThread]);
});
NSLog(@"end");
运行结果:
2019-07-19 17:14:10.249914+0800 OCTest[95069:18529064] end
2019-07-19 17:14:11.102118+0800 OCTest[88247:18500159] 1<NSThread: 0x1005346b0>{number = 2, name = (null)}
2019-07-19 17:14:11.102115+0800 OCTest[88247:18500157] 3<NSThread: 0x100644030>{number = 4, name = (null)}
2019-07-19 17:14:11.102115+0800 OCTest[88247:18500158] 2<NSThread: 0x100724760>{number = 3, name = (null)}
2019-07-19 17:14:12.107157+0800 OCTest[88247:18500159] 1-1<NSThread: 0x1005346b0>{number = 2, name = (null)}
2019-07-19 17:14:12.107157+0800 OCTest[88247:18500158] 2-1<NSThread: 0x100724760>{number = 3, name = (null)}
2019-07-19 17:14:12.107157+0800 OCTest[88247:18500157] 3-1<NSThread: 0x100644030>{number = 4, name = (null)}
由运行结果可以看出:
- 开启了多个新线程
- 代码块在不同的线程中异步执行,执行顺序不再遵循我们添加的顺序
- 线程不会被堵塞
总结:
由上面四个例子不难得出:
- 开不开线程和执行任务的函数有关
同步(dispatch_sync) – 不开线程,阻塞当前线程
异步(dispatch_async) – 开新线程,不会阻塞线程 - 开线程数和队列有关
串行队列 开一条
并行队列 开n条
线程死锁
在主线程中执行如下代码
+ (void)deadLockTest {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"dead lock");
});
}
会出现线程死锁现象,那么为什么会造成这样的情况呢?首先主队列本质是一个串行队列,串行队列在进行同步执行的时候是会阻塞当前线程。我们可以理解为,我们在主队列中提交了 deadLockTest 任务,这个时候又同步添加了新的任务1,所以这时候在主线程任务的执行顺序应该是deadLockTest 执行完毕在执行任务1。但是添加同步任务1时,线程阻塞了,必须执行完任务1后,才能继续执行任务deadLockTest,但是任务deadLockTest没有执行完毕,有无法去执行任务1的,两者进入了一个死循环,这样就造成了线程死锁现象。
由这个例子可以得知:在日常开发中,我们不应该在串行队列中,去嵌套添加同步执行的任务,否则会早成线程死锁。