iOS多线程-GCD
简介
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。--摘自百度百科。
GCD是一套可以充分合理的利用CPU资源,同时可以处理比较复杂的多线程问题的多线程解决方案。
优点
- 合理利用利用更多的CPU内核,提高CPU资源利用率
- 自动为任务创建分配线程、执行任务、销毁线程,所有工作都又GCD完成
- GCD提供了非常丰富的函数
使用
GCD是使用还是比较简单的,这里可以总结为一句话:队列以并行或串行的方式执行任务,所以要明白两个重要概念,队列和串行、并行。
队列
队列用来存放待执行的任务,队列按照FIFO(先进先出)执行任务,即先加入队列的任务先被执行,后加入的排队等待,执行完的任务从队列中释放。队列分为两种,串行队列和并行队列。
- 串行队列(Serial Dispatch Queue),任务严格的按照加入队列的顺序一个一个被执行,一个任务要想被执行必须等它前面的所有任务执行完成,并且系统分配给串行队列的线程还有一条,所有任务都在一条线程上按照FIFO的规则完成。
- 并行队列(Concurrent Dispatch Queue)并行队列也是按照FIFO的的规则执行任务的,但是系统会根据当前队列的任务数、CPU的内核数、内存使用情况等因素动态分配多条线程给队列,CPU在多条线程上快速切换,所以看上去就像是同时执行多条线程一样。
- 创建队列,GCD使用
dispatch_queue_create
创建队列,方法有两个参数,第一个参数是队列的唯一标识符,第二个参数是用来表示队列是并行的还是串行的,传入DISPATCH_QUEUE_SERIAL
或NULL
表示串行队列,传入DISPATCH_QUEUE_CONCURRENT
表示并行队列,
// 串行队列
dispatch_queue_t queue= dispatch_queue_create("SerialTest.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue= dispatch_queue_create("SerialTest.queue", NULL);
// 并行队列
dispatch_queue_t queue= dispatch_queue_create("Concurrent Test.queue", DISPATCH_QUEUE_CONCURRENT);
- 主队列和全局并发队列,系统默认创建了两个队列,主队列
dispatch_get_main_queue()
又叫UI线程,用于刷新UI,所以如果有一些耗时操作,一定不要放在主线程里,会导致界面卡死。全局并发队列dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
第一个参数表示优先级,一般设为DISPATCH_QUEUE_PRIORITY_DEFAULT
第二个参数没什么用,一般设为0,全局并发队列是常用的,如果用到并发队列我们一般加到全局队列中,不用自己创建。
同步和异步
GCD提供了这两个函数dispatch_sync
和dispatch_async
分别表示同步执行和异步执行,同步和异步的主要区别是是否会阻塞当前线程所,调用dispatch_sync
会阻塞调用该函数的线程,直到block里的任务执行完成函数才会返回,当前线程才会继续执行。dispatch_async
不会阻塞当前线程,当前线程会继续往下执行。
系统会根据调用的同步或异步函数和任务加入的队列类型来决定是否需要新开线程,有如下几种组合方式:
串行队列 | 并行队列 | 主队列 | |
---|---|---|---|
同步执行 | 不会开新线程,串行执行 | 不会开新线程,串行执行 | 可能会线程死锁 |
异步执行 | 会开一条新线程,串行执行 | 会开新线程,并行执行 | 不会开新线程,串行执行 |
我们在开发中最常用的组合是异步执行并行队列,当程序要执行一些例如下载、上传数据等耗时操作时,如果不想阻塞UI线程,就可以把耗时操作交给GCD来处理。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
/*
......
耗时操作
......
*/
//任务完成,返回主线程刷洗UI
dispatch_async(dispatch_get_main_queue(), ^{
//刷新UI
});
});
GCD线程死锁
线程死锁指的是两个线程互相等待对方执行完成从而造成谁也无法执行,由于GCD的同步执行函数dispatch_sync
会阻塞当前线程,所以经常会造成线程死锁,当使用dispatch_sync
时要格外注意。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务执行前 %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"执行任务 %@",[NSThread currentThread]);
});
NSLog(@"执行任务后 %@",[NSThread currentThread]);
}
运行以上代码,在打印完NSLog(@"任务执行前 %@",[NSThread currentThread]);
这句后,直接抛出如下异常
以上异常就是由于线程死锁引起的,当程序执行到
dispatch_sync
函数,这个函数会把当前线程(主线程
)阻塞掉,然后把任务加入到主队列中,也就是把任务交给主线程来执行,然后这时候主线程是阻塞状态,所以block里的任务不会被执行,而主线程要想执行后面的代码,必须要等待dispatch_sync
函数返回,这就造成了死锁。