多线程 - GCD
GCD
1.简介
1.1 GCD官方解释
Grand Central Dispatch(GCD)是异步执行任务的技术之一,一般将应用程序中记述的线程管理用的代码再系统级中实现。开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。
1.2 GCD的好处
- GCD可用于多核的并行运算
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
2.任务和队列
学习GCD之前,我们要了解GCD中两个核心概念:任务和队列。
2.1 任务
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是是否具备开启新线程的能力。
- 同步执行:只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步执行:可以在新的线程中执行任务,具备开启新线程的能力。
2.2 队列
队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列和并行队列。
- 并行队列:可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务),并行功能只有在异步函数下才有效。
- 串行队列:让任务一个接着一个的执行(一个任务执行完毕后,再执行下一个任务)
3.GCD的使用步骤
GCD的使用步骤其实很简单,只有两步。
1.创建一个队列(串行队列或并行队列)
2.将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
3.1 队列的创建方法
可以使用 dispatch_queue_create 来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于Debug,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并行队列。
// 串行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); // 并行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
在下图中,我们可以看到,唯一标识符的作用,在Debug时,更容易确定是哪个线程除了问题。
对于并行队列,还可以使用 dispatch_get_global_queue 来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT,第二个参数暂时没用,用0即可。
3.2 任务的创建方法
// 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 }); // 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 });
虽然使用GCD只需两步,但是既然我们有两种队列,两种任务执行方式,那么我们就有了四种不同的组合方式,这4种不同的组合方式是:
- 并行队列 + 同步执行
- 并行队列 + 异步执行
- 串行队列 + 同步执行
- 串行队列 + 异步执行
实际上,我们还有一种特殊队列是主队列,那样就有6种不同的组合方式:
- 主队列 + 同步执行
- 主队列 + 异步执行
那么这几种不同的组合方式有什么区别呢,看图:
并行队列 | 串行队列 | 主队列 | |
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 |
异步(async) | 有开启新线程,并行执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
4. GCD的基本使用
4.1 并行队列 + 同步执行
不会开启新线程,执行完一个任务,再执行下一个任务
- (void)lesson2{ // 创建一个并行队列 NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent---end"); /* 我们可以看出,任务都是在主线程中执行的,由于只有一个线程,所以任务只能一个一个执行 同时,我们还可以看到,都打印在 begin 和 end之间,说明,任务添加到队列之后,马上会执行的 */ // 2017-12-06 18:30:42.338766+0800 GCD[20449:496505] syncConcurrent---begin // 2017-12-06 18:30:42.339001+0800 GCD[20449:496505] 1 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339187+0800 GCD[20449:496505] 1 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339325+0800 GCD[20449:496505] 1 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339448+0800 GCD[20449:496505] 2 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339603+0800 GCD[20449:496505] 2 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339763+0800 GCD[20449:496505] 2 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339919+0800 GCD[20449:496505] 3 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.340269+0800 GCD[20449:496505] 3 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.340406+0800 GCD[20449:496505] 3 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.340537+0800 GCD[20449:496505] syncConcurrent---end }
4.2 并行队列 + 异步执行
可同时开启多线程,任务交替执行
- (void)lesson3{ // 创建一个并行队列 NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent---end");
/*
我们可以看出,除了主线程,又开启了3个线程,并且任务是交替同时执行的,另一方面可以看出,任 务不是马上执行,而是将所有任务添加到队列之后,才开始异步执行。
*/ // 2017-12-06 22:16:40.036449+0800 GCD[1001:46014] syncConcurrent---begin // 2017-12-06 22:16:40.036735+0800 GCD[1001:46014] syncConcurrent---end // 2017-12-06 22:16:40.036914+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.036912+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} // 2017-12-06 22:16:40.036956+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.037143+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.037306+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.037337+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} // 2017-12-06 22:16:40.037505+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.037608+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.039677+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} }
4.3 串行队列 + 同步执行
不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行另一个任务
- (void)lesson4{ NSLog(@"syncConcurrent - begin"); dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent - end");
/* 我们可以看到,所有的任务都是在主线程中执行的,并没有开启新的线程,而且由于是串行队列,所以按顺序一个一个执行,而且任务都是添加到队列中马上执行的。
*/ // 2017-12-06 22:23:10.808603+0800 GCD[1066:51102] syncConcurrent - begin // 2017-12-06 22:23:10.809058+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809336+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809539+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809706+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809884+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810055+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810226+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810494+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810713+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810813+0800 GCD[1066:51102] syncConcurrent - end }
4.4 串行队列 + 异步执行
会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
- (void)lesson5{ NSLog(@"syncConcurrent - begin"); dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } });
/* 我们可以看到,开启了一条新线程,但是任务还是串行,所以一个一个执行,任务不是马上执行,而是将所有任务添加到队列后才开始同步执行。*/ NSLog(@"syncConcurrent - end"); // 2017-12-06 22:25:04.040810+0800 GCD[1082:52532] syncConcurrent - begin // 2017-12-06 22:25:04.041050+0800 GCD[1082:52532] syncConcurrent - end // 2017-12-06 22:25:04.041171+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.041814+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.042084+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.043690+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.043849+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.044082+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.044733+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.045220+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.045922+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} }
主队列:GCD自带的一种特殊的串行队列,所有放在主队列中的任务,都会放到主线程中执行,可以通过dispatch_get_main_queue() 获得主队列。
4.5 主队列 + 同步执行
互等卡住不可行(在主线程中调用)
- (void)lesson6{ NSLog(@"主线程 + 同步执行 - begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } });
/*因为,我们在主线程中执行这段代码。我们把任务放到了主队列中,也就是放到了主线程的队列中。而同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它就会立马执行,但是主线程正在处理 lesson6 的方法,所以任务1需要等待 lesson6 执行完成才能执行,当lesson6执行到任务1的使用,又需要等任务1执行完成才能执行任务2和任务3,
那么,现在的情况就是 lesson6 方法和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了。*/
因为,此时主线程正在执行 方法lesson6, 但是由于是同步执行, 当将任务1添加到队列中时会马上执行,但是由于主线程正在执行方法lesson6,任务1需要等待lesson6执行完成,当lesson6执行到任务1的时候,也需要任务1执行完成才能继续执行,所以相互等待。
NSLog(@"主线程 + 同步执行 - end"); // 2017-12-06 22:31:04.600063+0800 GCD[1120:56056] 主线程 + 同步执行 - begin // (lldb) }
那么,如果不在主线程中调用,而在其它线程中调用会如何呢?
不会开启新线程,执行完一个任务,在执行下一个任务(在其它线程中调用)
- (void)lesson7{ dispatch_queue_t queue = dispatch_queue_create("111", NULL); dispatch_async(queue, ^{ [self lesson6]; }); /*所有任务都是在主线程中执行的,并没有开启新的线程,而且由于主队列是串行队列,所以按顺序一个一个执行,并且任务添加到队列中就会马上执行*/ // 2017-12-06 22:35:25.873497+0800 GCD[1140:58833] 主线程 + 同步执行 - begin // 2017-12-06 22:35:25.878078+0800 GCD[1140:58563] 1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.878277+0800 GCD[1140:58563] 1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.878440+0800 GCD[1140:58563] 1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.879158+0800 GCD[1140:58563] 2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.879723+0800 GCD[1140:58563] 2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.880138+0800 GCD[1140:58563] 2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.880857+0800 GCD[1140:58563] 3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.881020+0800 GCD[1140:58563] 3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.881485+0800 GCD[1140:58563] 3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.881695+0800 GCD[1140:58833] 主线程 + 同步执行 - end }
4.6 主队列 + 异步执行
只有在主线程中执行任务,执行完一个任务,再执行下一个任务
- (void)lesson8{ NSLog(@"主线程 + 异步执行 - begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); /*我们发现所有任务都是在主线程中,虽然是异步执行,具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中,并一个一个执行,并且任务不是马上执行,而是将所有任务添加到队列之后,才开始同步执行。
*/ NSLog(@"主线程 + 异步执行 - end"); // 2017-12-06 22:40:01.452127+0800 GCD[1176:61800] 主线程 + 异步执行 - begin // 2017-12-06 22:40:01.452394+0800 GCD[1176:61800] 主线程 + 异步执行 - end // 2017-12-06 22:40:01.457218+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.457404+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.457966+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458234+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458409+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458602+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458829+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458990+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.459240+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} }
5.GCD线程之间的通讯
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其它线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (int i = 0; i < 2; ++i) { NSLog(@"1------%@",[NSThread currentThread]); } // 回到主线程 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"2-------%@",[NSThread currentThread]); }); });