Dispatch Queue挂起 安全
dispatch queue能够被挂起和恢复。使用 dispatch_suspend函数来挂起,使用 dispatch_resume 函数来恢复。这两个函数的行为是如你所愿的。另外,这两个函数也能够用于dispatch source。 多线程
一个要注意的地方是,dispatch queue的挂起是block粒度的。换句话说,挂起一个queue并不会将当前正在执行的block挂起。它会容许当前执行的block执行完毕,而后后续的block再也不会被执行,直至queue被恢复。 函数
还有一个注意点:从man页上得来的:若是你挂起了一个queue或者source,那么销毁它以前,必须先对其进行恢复。 测试
Dispatch Queue目标指定 spa
全部的用户队列都有一个目标队列概念。从本质上讲,一个用户队列其实是不执行任何任务的,可是它会将任务传递给它的目标队列来执行。一般,目标队列是默认优先级的全局队列。 .net
用户队列的目标队列能够用函数 dispatch_set_target_queue来修改。咱们能够将任意dispatch queue传递给这个函数,甚至能够是另外一个用户队列,只要别构成循环就行。这个函数能够用来设定用户队列的优先级。好比咱们能够将用户队列的目标队列设定为低优先级的全局队列,那么咱们的用户队列中的任务都会以低优先级执行。高优先级也是同样道理。 线程
有一个用途,是将用户队列的目标定为main queue。这会致使全部提交到该用户队列的block在主线程中执行。这样作来替代直接在主线程中执行代码的好处在于,咱们的用户队列能够单独地被挂起和恢复,还能够被重定目标至一个全局队列,而后全部的block会变成在全局队列上执行(只要你确保你的代码离开主线程不会有问题)。 指针
还有一个用途,是将一个用户队列的目标队列指定为另外一个用户队列。这样作能够强制多个队列相互协调地串行执行,这样足以构建一组队列,经过挂起和暂停那个目标队列,咱们能够挂起和暂停整个组。想象这样一个程序:它扫描一组目录而且加载目录中的内容。为了不磁盘竞争,咱们要肯定在同一个物理磁盘上同时只有一个文件加载任务在执行。而但愿能够同时从不一样的物理磁盘上读取多个文件。要实现这个,咱们要作的就是建立一个dispatch queue结构,该结构为磁盘结构的镜像。 code
首先,咱们会扫描系统并找到各个磁盘,为每一个磁盘建立一个用户队列。而后扫描文件系统,并为每一个文件系统建立一个用户队列,将这些用户队列的目标队列指向合适的磁盘用户队列。最后,每一个目录扫描器有本身的队列,其目标队列指向目录所在的文件系统的队列。目录扫描器枚举本身的目录并为每一个文件向本身的队列提交一个block。因为整个系统的创建方式,就使得每一个物理磁盘被串行访问,而多个物理磁盘被并行访问。除了队列初始化过程,咱们根本不须要手动干预什么东西。 队列
信号量
dispatch的信号量是像其余的信号量同样的,若是你熟悉其余多线程系统中的信号量,那么这一节的东西再好理解不过了。
信号量是一个整形值而且具备一个初始计数值,而且支持两个操做:信号通知和等待。当一个信号量被信号通知,其计数会被增长。当一个线程在一个信号量上等待时,线程会被阻塞(若是有必要的话),直至计数器大于零,而后线程会减小这个计数。
咱们使用函数 dispatch_semaphore_create 来建立dispatch信号量,使用函数 dispatch_semaphore_signal 来信号通知,使用函数 dispatch_semaphore_wait 来等待。这些函数的man页有两个很好的例子,展现了怎样使用信号量来同步任务和有限资源访问控制。
单次初始化
GCD还提供单词初始化支持,这个与pthread中的函数 pthread_once 很类似。GCD提供的方式的优势在于它使用block而非函数指针,这就容许更天然的代码方式:
这个特性的主要用途是惰性单例初始化或者其余的线程安全数据共享。典型的单例初始化技术看起来像这样(线程安全的):
+ (id)sharedWhatever { static Whatever *whatever = nil; @synchronized([Whatever class]) { if(!whatever) whatever = [[Whatever alloc] init]; } return whatever; }
这挺好的,可是代价比较昂贵;每次调用 +sharedWhatever 函数都会付出取锁的代价,即便这个锁只须要进行一次。确实有更风骚的方式来实现这个,使用相似双向锁或者是原子操做的东西,可是这样挺难弄并且容易出错。
使用GCD,咱们能够这样重写上面的方法,使用函数 dispatch_once:
+ (id)sharedWhatever { static dispatch_once_t pred; static Whatever *whatever = nil; dispatch_once(&pred, ^{ whatever = [[Whatever alloc] init]; }); return whatever; }
这个稍微比 @synchronized 方法简单些,而且GCD确保以更快的方式完成这些检测,它保证block中的代码在任何线程经过 dispatch_once 调用以前被执行,但它不会强制每次调用这个函数都让代码进行同步控制。实际上,若是你去看这个函数所在的头文件,你会发现目前它的实现实际上是一个宏,进行了内联的初始化测试,这意味着一般状况下,你不用付出函数调用的负载代价,而且会有更少的同步控制负载。
结论
这一章,咱们介绍了dispatch queue的挂起、恢复和目标重定,以及这些功能的一些用途。另外,咱们还介绍了如何使用dispatch 信号量和单次初始化功能。到此,我已经完成了GCD如何运做以及如何使用的介绍。