GCD的API有很多,学习了比较重要的API,这里学习GCD的实现
GCD是异步执行任务的技术之一,GCD规定我们只需要定义想执行的任务并且追加到适当的Dispatch Queue里,GCD就能生成必要的线程并计划执行任务。
回顾一下任务和队列
学习GCD之前,先来了解 GCD 中两个核心概念:『任务
』 和 『队列
』。
就是执行操作的意思,在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力
同步执行和异步执行
同步执行sync
异步执行async
队列(Dispatch Queue
):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO
(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务
串行队列和并发队列
GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async。
// 串行队列的创建方法
dispatch_queue_t serialQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
由于上述的不同队列和任务的创建,加上之前的主队列和全局队列,对于全局队列我们可以理解为普通的队列,所以只需要对主队列进行研究这样我们出现了6种研究方法
除了上边提到的『主线程』中调用『主队列』+『同步执行』会导致死锁问题。实际在使用『串行队列』的时候,也可能出现阻塞『串行队列』所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。
比如下面代码这样:在『异步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。
- (void)lock {
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
解释:执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了**『串行队列』**,最终造成了 串行队列所在的线程(子线程)死锁问题。
主队列造成死锁也是基于这个原因,所以,这也进一步说明了主队列其实并不特殊
对于队列和任务,这里简单的总结。
『不同队列』+『不同任务』 组合,以及 『队列中嵌套队列』 使用的区别:
『异步执行 + 并发队列』就是:系统开启了多个线程(主线程+其他子线程),任务可以多个同时运行。
『同步执行 + 并发队列』就是:系统只默认开启了一个主线程,没有开启子线程,虽然任务处于并发队列中,但也只能一个接一个执行了。
/**
* 同步执行 + 并发队列
* 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncConcurrent---end");
}
// 串行队列的创建方法
dispatch_queue_t serialQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
// 同步执行任务创建方法
dispatch_sync(serialQueue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(concurrentQueue, ^{
// 这里放异步执行任务代码
});
/**
* 同步执行 + 串行队列
* 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
*/
- (void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncSerial---end");
}
/**
* 异步执行 + 串行队列
* 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
*/
- (void)asyncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncSerial---end");
}
主队列:GCD 默认提供的 串行队列。
默认情况下,平常所写代码是直接放在主队列中的。
所有放在主队列中的任务,都会放到主线程中执行。
可使用 dispatch_get_main_queue() 获得主队列。
/**
* 同步执行 + 主队列
* 特点(主线程调用):互等卡主不执行。
* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncMain---end");
}
要是如果不在主线程中调用,而在其他线程中调用会如何呢?
设计到了创建新线程
- (void)getOtherThread {
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
}
在其他线程中使用 同步执行 + 主队列 可看到:
不同于主线程的原因:因为syncMain 任务 放到了其他线程里,而 任务 1、任务 2、任务3 都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务 在其他线程中执行到追加 任务 1 到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的 任务1,等 任务1 执行完毕,再接着执行 任务 2、任务 3。所以这里不会卡住线程,也就不会造成死锁问题
/**
* 异步执行 + 主队列
* 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
*/
- (void)asyncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncMain---end");
}
根据GCD的需求,我们一般把比较耗费时间的操作,放在其他的线程里面,而对于UI界面的刷新,点击等响应事件则是主线程进行的操作
而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
GCD的实现和API一样重要,主要解释了如何将串行并行和同步异步任务结合达到提升进程的效果。