标签:
关于iOS开发中的多线程,一直是工作中的重要组成部分。由于难以理解且对app的用户体验影响重大,也是面试中的考察重点。
自己虽然以前有学习iOS多线程的部分,但由于当时对iOS开发还处于懵懂阶段,很多地方理解可能均有问题,遂查阅一些资料重温了GCD的相关内容,并撰写此文已记录自己的学习路程。
Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code executikkon on multicore hardware in iOS and OS X.
GCD术语中有两个核心的概念:任务,队列。
在本文中,我们可以将任务暂定为objc中的一个block。我们可以把任务与队列都看成是objc中的对象。任务与队列都有他们自己的属性与行为。
任务的属性是:同步or异步
队列的属性是:并发or串行
串行与并发是队列派发任务的行为描述,可以理解为队列的属性。既然是队列,那么必定遵循FIFO的原则。无论是串行还是并发队列,一定都是先进先出即先进入队列的一定会被先执行,但是不一定先完成。
串行含义就是一个接一个的派发任务,注意理解一个接一个,即为要等待上一个任务完成了,才会派发下一个任务。这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
串行队列
虽然串行队列只有在上一个任务执行完毕了才会派发下一个任务,但这并不意味着下一个任务就会被立即执行。注意理解执行与派发,执行是GCD来做的,而派发是由队列来做的。
并行的意义就与串行相反,虽然它也会按照FIFO原则派发任务,但是它并不会等待上一个任务完成后才会派发下一个任务,而是直接将任务抛出。
并发队列
就像图中那样,四个任务都会按照你添加的顺序去执行,and that’s about all you’re guaranteed!与串行队列同样,任务何时去执行,同时有几个任务去执行等都完全取决于GCD。
ps:注意此处是==并发==不是==并行==,二者不可混淆。若有兴趣,可参考
所谓的“并发”,英文翻译是concurrent。而并行是parallelism。
并发指的是一种现象,一种经常出现,无可避免的现象。它描述的是“多个任务同时发生,需要被处理”这一现象。它的侧重点在于“发生”。
比如有很多人排队等待检票,这一现象就可以理解为并发。
并行指的是一种技术,一个同时处理多个任务的技术。它描述了一种能够同时处理多个任务的能力,侧重点在于“运行”。
比如景点开放了多个检票窗口,同一时间内能服务多个游客。这种情况可以理解为并行。
首先,系统提供给你一个叫做 主队列(main queue) 的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发送消息给 UIView 或发送通知的。
系统同时提供给你好几个并发队列。它们叫做 全局调度队列(Global Dispatch Queues) 。目前的四个全局队列有着不同的优先级:background、low、default 以及 high。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
最后,你也可以创建自己的串行队列或并发队列。这就是说,至少有五个队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。
以上是调度队列的大框架!
上文提到同步与异步都是任务对象的“属性”,这个属性的作用就是标志着任务被执行的方式:
同步的含义是该任务执行时,需要等这个任务完成了,才继续线程中的下一个任务。如果你对多线程有一定的了解,你能立即领悟到同步肯定是在当前线程中执行任务的,而需要等待这个任务完成的特性使得同步任务必然会阻塞当前线程。
与同步相反,异步任务并不需要被等待。即线程执行异步任务时,不会阻塞住当前线程。但是需要注意的是:执行异步任务时,并不一定会开辟新的线程。原因是在主队列进行任务时,无论是异步还是同步,都将会被放在主线程中。
放个表格总结下:
example |
同步 |
异步 |
主队列 |
主线程 |
主线程 |
串行队列 |
当前线程 |
开辟线程 |
并发队列 |
当前线程 |
开辟线程 |
注:一定要理解好“派发”与“执行”之间的关系,其实这个地方用对象来理解队列,线程,GCD,串行or并行是很方便的。
一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。
线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDict;ionary 就不是线程安全的,应该保证一次只能有一个线程访问它。
线程死锁是使用GCD使用不当时的常见问题,尤其是主线程中,需要格外小心。造成线程死锁的原因是同步执行某个任务A,但是这个任务又在串行队列的后方。此时GCD要求任务A立即执行,即阻塞住线程。可队列是要求遵循先进先出的原则,于是线程死锁。
举个栗子
//同步串行队列
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{//主队列中同步执行任务2
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
这样使用GCD是会导致主线程死锁的,控制台只会输出“==1==”。这是由于任务1先被加入主队列,之后是任务3,再之后才是任务2。即队列中:
<—————————— 出队顺序
任务1 --- 任务3 --- 任务2(此处造成线程死锁,2必须立马执行,但是出队顺序在3的后面)
由于3任务卡在2任务前,导致2任务无法被执行。但由于2任务是同步任务,必须先被执行,于是3等2,2等3造成线程死锁。
原文来自:简书/ZhengLi
标签: