GCD
即Grand Central Dispatch
是苹果推出的的多线程管理框架, 也是开发中常用到的工具之一, 它具有自动管理线程的优势, 为开发工作提供了很大的便利。
GCD
的官方文档
队列
所谓队列就是指一连串的任务, 这些任务可以顺序执行, 也可以同时执行。这也就对应我们平时说的串行队列和并发队列。GCD
官方文档一开始就给我们介绍了两种队列
dispatch_get_main_queue
1 |
|
在主线程上运行的队列, 平时一般使用是在非主线程时回到主线程更新UI, 它是一个串行队列。
dispatch_get_global_queue
1 |
|
系统所定义的一种全局队列, 可以通过制定优先级去完成不同的任务, 它默认是一个并发队列。
同步和异步
简单来说, 同步是指在调用某个方法后, 必须得到返回的值, 才能进行下一步操作(会阻塞后续操作)。而异步和同步的不同是它不用得到返回的值可以继续后续操作(不会阻塞后面的操作)。
经典面试题, 下面代码将会输出什么?
1 |
|
来自官方文档的警告
Important
Attempting to synchronously execute a work item on the main queue results in deadlock.
如果换成dispatch_async
则会正常输出
1 |
|
创建队列
1 |
|
根据文档, 我们可以得出第一个参数是一个唯一标识符用于调试和定位, 也可以为空的, 第二个参数是队列类型, 有串行和并发。
有了队列之后, 我们就可以分别测试同步和异步在队列中的效果了, 使用[NSThread currentThread]
来观察队列所在线程的情况
同步串行队列
1 |
|
执行结果
1 |
|
同步+并发 这种方式是完全按照顺序输出的
异步串行队列
由于是异步执行, 我们可以现在队列的加上开始中间和结束试一试
1 |
|
经过多次的运行后, 有时会出现这样的结果
1 |
|
异步+串行 这种方式的任务也是一个一个执行的, 即使有堵塞也要等待, 但有一个问题, 异步执行开启了新线程, 可以看到中间
和异步 串行1
同时执行了
同步并发队列
1 |
|
多次执行后的输出结果
1 |
|
同步+并发 这种方式是完全按照顺序输出的, 有可能有理解误区在并发上面, 实际上都是在主线程执行, 并没有多线程执行
异步并发队列
1 |
|
这次输出的结果就比较凌乱了
1 |
|
异步+并发这种方式是真正的并发+多线程, 有堵塞的任务会最后执行, 且线程都不相同
并发队列是否可以有序执行?
答案是可以的。可以利用dispatch_barrier_async
1 |
|
结果:
1 |
|
异步栅栏可以分割异步任务, 及异步执行1,2 与异步执行3, 同步栅栏可以起到线程同步的作用, 即最后打印”结束了”。
小结
- 同步执行, 不会开启新的线程, 无论是串行或并发的同步任务, 都以串行的方式执行
- 异步执行, 会开启新的线程, 串行即一个一个的执行, 并发即一起执行
调度组
多条队列是可以有顺序执行的。
队列的优先级
1 |
|
观察这样的一段代码
1 |
|
队列 2,3,4 总是会优先于 1和-1 执行, 无论他们是什么类型的队列, 起决定性的参数为dispatch_queue_attr_make_with_qos_class
中的第二个参数, 文档中代码的顺序表示优先级从高到低。
但这只是默认的顺序执行而已, 如果上面其中有一个队列的任务堵塞, 其他队列的执行是不受影响的, 如果要控制所有队列的执行顺序就需要用到调度组。
dispatch_group_t
为了方便队列管理, 苹果推出了调度组, 方便我们进行队列管理。一个调度组的基本写法:
1 |
|
统一回调
dispatch_group_wait
有些时候我们希望调度组中的任务执行完毕之后统一来做一些事情, 对于上面这种block内部完全是同步执行的, 可以用long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
来进行等待操作:
1 |
|
如果上面的其中一个dispatch_group_async
有异步执行, 就不能再用这个函数了。实际上我们所遇到的更多也是异步执行任务, 那么就要用到下面的方法:
dispatch_group_notify
这个方法还与另外两个方法有关联, 他们是dispatch_group_enter
与dispatch_group_leave
, 我们以网络请求为例子:
1 |
|
注意添加和离开要成对出现, 当所有的离开都执行完毕字后, 会调用dispatch_group_notify
来执行统一回调。
结果:
1 |
|
dispatch_semaphore_t
上面的例子适用于多种任务同时执行之后的回调, 但是现在有这么一种情况, 我想只做一件事情, 而且这件事情要做好多次, 在达到某个节点的时候进行回调, 有没有更好的解决方式呢? 这个时候就会用到另一个对象: 信号量。
1 |
|
上述例子中, 下标的打印和请求成功的打印总是会同对出现, 而且是顺序的:
1 |
|
信号量总结: 当调用dispatch_semaphore_wait
会减少信号量计数, 当信号量为0的情况下会阻塞当前线程, 而dispatch_semaphore_signal
会增加信号量计数, 利用这个特性我们仍然可以等所有异步操作完成之后再去做一些事情, 但弊端就是会堵塞当前线程。
其他方法
dispatch_after
延迟执行放方法dispatch_after
1 |
|
dispatch_once
1 |
|
dispatch_apply
这个东西类似于迭代器, 一个block中的方法会执行指定的次数:
1 |
|
注意它的线程输出:
1 |
|
在主线程和自己创建的线程之间交互输出的, 并且会阻塞当前线程。 试一下在异步并发中执行:
1 |
|
结果:
1 |
|
这样就全在一条队列上执行了。
其余的待补充中。。。