Grand Central Dispatch(GCD)是异步运行任务的技术之中的一个。

一般将应用程序中记述的线程管理用的代码在系统级中实现。开发人员仅仅须要定义想运行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划运行任务。因为线程管理是作为系统的一部分来实现的。因此可统一管理。也可运行任务,这样就比曾经的线程更有效率。

Dispatch Queue

Dispatch Queue是用来运行任务的队列,是GCD中最主要的元素之中的一个。

Dispatch Queue分为两种:

  • Serial Dispatch Queue,按加入进队列的顺序(先进先出)一个接一个的运行
  • Concurrent Dispatch Queue。并发运行队列里的任务
简而言之。Serial Dispatch Queue仅仅使用了一个线程,Concurrent Dispatch Queue使用了多个线程(详细使用了多少个,由系统决定)。

能够通过两种方式来获得Dispatch Queue。第一种方式是自己创建一个:

let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)

第一个參数是队列的名称,通常是使用倒序的全域名。

尽管能够不给队列指定一个名称,可是有名称的队列能够让我们在遇到问题时更好调试;当第二个參数为nil时返回Serial Dispatch Queue,如上面那个样例。当指定为DISPATCH_QUEUE_CONCURRENT时返回Concurrent Dispatch Queue。

须要注意一点,假设是在OS X 10.8或iOS 6以及之后版本号中使用,Dispatch Queue将会由ARC自己主动管理,假设是在此之前的版本号。须要自己手动释放,例如以下:

let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)

dispatch_async(myQueue, { () -> Void in

println("in Block")

})

dispatch_release(myQueue)

以上是通过手动创建的方式来获取Dispatch Queue,另外一种方式是直接获取系统提供的Dispatch Queue。

要获取的Dispatch Queue无非就是两种类型:

  • Main Dispatch Queue
  • Global Dispatch Queue / Concurrent Dispatch Queue
一般仅仅在须要更新UI时我们才获取Main Dispatch Queue,其它情况下用Global Dispatch Queue就满足需求了:

//获取Main Dispatch Queue

let mainQueue = dispatch_get_main_queue()

//获取Global Dispatch Queue

)

得到的Global Dispatch Queue实际上是一个Concurrent Dispatch Queue。Main Dispatch Queue实际上就是Serial Dispatch Queue(而且仅仅有一个)。

获取Global Dispatch Queue的时候能够指定优先级。能够依据自己的实际情况来决定使用哪种优先级。
普通情况下。我们通过另外一种方式获取Dispatch Queue即可了。

dispatch_after

dispatch_after能让我们加入进队列的任务延时运行,比方想让一个Block在10秒后运行:

* NSEC_PER_SEC))

dispatch_after(time, globalQueue) { () -> Void in

println("在10秒后运行")

}

NSEC_PER_SEC表示的是秒数,它还提供了NSEC_PER_MSEC表示毫秒。

上面这句dispatch_after的真正含义是在10秒后把任务加入进队列中,并非表示在10秒后运行,大部分情况该函数能达到我们的预期,仅仅有在对时间要求很精准的情况下才可能会出现故障。

获取一个dispatch_time_t类型的值能够通过两种方式来获取,以上是第一种方式。即通过dispatch_time函数。还有一种是通过dispatch_walltime函数来获取,dispatch_walltime须要使用一个timespec的结构体来得到dispatch_time_t。通常dispatch_time用于计算相对时间。dispatch_walltime用于计算绝对时间。我写了一个把NSDate转成dispatch_time_t的Swift方法:

func getDispatchTimeByDate(date: NSDate) -> dispatch_time_t {

let interval = date.timeIntervalSince1970

var second = 0.0

let subsecond = modf(interval, &second)

var time = timespec(tv_sec: __darwin_time_t(second), tv_nsec: (Int)(subsecond
* (Double)(NSEC_PER_SEC)))

)

}

这种方法接收一个NSDate对象,然后把NSDate转成dispatch_walltime须要的timespec结构体,最后再把dispatch_time_t返回,相同是在10秒后运行,之前的代码在调用部分须要改动成:

))

dispatch_after(time, globalQueue) { () -> Void in

println("在10秒后运行")

}

这就是通过绝对时间来使用dispatch_after的样例。

dispatch_group

可能常常会有这样一种情况:我们如今有3个Block要运行,我们不在乎它们运行的顺序。我们仅仅希望在这3个Block运行完之后再运行某个操作。

这个时候就须要使用dispatch_group了:

)

let group = dispatch_group_create()

dispatch_group_async(group, globalQueue) { () -> Void in

println("1")

}

dispatch_group_async(group, globalQueue) { () -> Void in

println("2")

}

dispatch_group_async(group, globalQueue) { () -> Void in

println("3")

}

dispatch_group_notify(group, globalQueue) { () -> Void in

println("completed")

}

输出的顺序与加入进队列的顺序无关,由于队列是Concurrent Dispatch Queue,但“completed”的输出一定是在最后的:
  1. 312
  2. completed

除了使用dispatch_group_notify函数能够得到最后运行完的通知外。还能够使用

)

let group = dispatch_group_create()

dispatch_group_async(group, globalQueue) { () -> Void in

println("1")

}

dispatch_group_async(group, globalQueue) { () -> Void in

println("2")

}

dispatch_group_async(group, globalQueue) { () -> Void in

println("3")

}

//使用dispatch_group_wait函数

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

println("completed")

须要注意的是。dispatch_group_wait实际上会使当前的线程处于等待的状态。也就是说假设是在主线程运行dispatch_group_wait。在上面的Block运行完之前,主线程会处于卡死的状态。

能够注意到dispatch_group_wait的第二个參数是指定超时的时间,假设指定为DISPATCH_TIME_FOREVER(如上面这个样例)则表示会永久等待,直到上面的Block所有运行完。除此之外。还能够指定为详细的等待时间。依据dispatch_group_wait的返回值来推断是上面block运行完了还是等待超时了。

最后,同之前创建dispatch_queue一样,假设是在OS X 10.8或iOS 6以及之后版本号中使用,Dispatch Group将会由ARC自己主动管理,假设是在此之前的版本号。须要自己手动释放。

dispatch_barrier_async

dispatch_barrier_async就如同它的名字一样,在队列运行的任务中添加“栅栏”,在添加“栅栏”之前已经開始运行的block将会继续运行,当dispatch_barrier_async開始运行的时候其它的block处于等待状态,dispatch_barrier_async的任务运行完后,其后的block才会运行。我们简单的写个样例。如果这个样例有读文件和写文件的部分:

func writeFile() {

,
forKey: "Integer_Key")

}

func readFile(){

print(NSUserDefaults.standardUserDefaults().integerForKey("Integer_Key"))

}

写文件仅仅是在NSUserDefaults写入一个数字7,读仅仅是将这个数字打印出来而已。我们要避免在写文件时候正好有线程来读取,就使用dispatch_barrier_async函数:

, forKey: "Integer_Key")

)

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_barrier_async(globalQueue) {self.writeFile() ; self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

我们先将一个9初始化到NSUserDefaults的Integer_Key中,然后在中间运行dispatch_barrier_async函数。因为这个队列是一个Concurrent Dispatch Queue。能同一时候并发多少线程是由系统决定的。假设加入dispatch_barrier_async的时候,其它的block(包含上面4个block)还没有開始运行,那么会先运行dispatch_barrier_async里的任务。其它block所有处于等待状态。

假设加入dispatch_barrier_async的时候,已经有block在运行了,那么dispatch_barrier_async会等这些block运行完后再运行。

dispatch_apply

dispatch_apply会将一个指定的block运行指定的次数。假设要对某个数组中的全部元素运行相同的block的时候,这个函数就显得非常实用了。使用方法非常easy。指定运行的次数以及Dispatch Queue,在block回调中会带一个索引。然后就能够依据这个索引来推断当前是对哪个元素进行操作: 

)

, globalQueue) { (index) -> Void in

print(index)

}

print("completed")

由于是Concurrent Dispatch Queue,不能保证哪个索引的元素是先运行的,可是“completed”一定是在最后打印,由于dispatch_apply函数是同步的,运行过程中会使线程在此处等待,所以一般的,我们应该在一个异步线程里使用dispatch_apply函数:

)

dispatch_async(globalQueue, { () -> Void in

, globalQueue) { (index) -> Void in

print(index)

}

print("completed")

})

print("在dispatch_apply之前")

dispatch_suspend / dispatch_resume

某些情况下,我们可能会想让Dispatch Queue临时停止一下,然后在某个时刻恢复处理。这时就能够使用dispatch_suspend以及dispatch_resume函数: 

//暂停

dispatch_suspend(globalQueue)

//恢复

dispatch_resume(globalQueue)

暂停时。假设已经有block正在运行,那么不会对该block的运行产生影响。dispatch_suspend仅仅会对还未開始运行的block产生影响。


Dispatch Semaphore

信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前。线程必须获取一个信号量,一旦该关键代码段完毕了,那么该线程必须释放信号量。

其他想进入该关键代码段的线程必须等待前面的线程释放信号量。

信号量的详细做法是:当信号计数大于0时。每条进来的线程使计数减1。直到变为0,变为0后其它的线程将进不来,处于等待状态;运行完任务的线程释放信号。使计数加1,如此循环下去。
以下这个样例中使用了10条线程,可是同一时候仅仅运行一条。其它的线程处于等待状态:

)

)

...  {

dispatch_async(globalQueue, { () -> Void in

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

let time = dispatch_time(DISPATCH_TIME_NOW,
(Int64 * NSEC_PER_SEC))

dispatch_after(time, globalQueue) { () -> Void in

print("2秒后运行")

dispatch_semaphore_signal(semaphore)

}

})

}

取得信号量的线程在2秒后释放了信息量。相当于是每2秒运行一次。

通过上面的样例能够看到,在GCD中,用dispatch_semaphore_create函数能初始化一个信号量。同一时候须要指定信号量的初始值;使用dispatch_semaphore_wait函数分配信号量并使计数减1。为0时处于等待状态;使用dispatch_semaphore_signal函数释放信号量,并使计数加1。
另外dispatch_semaphore_wait相同也支持超时。仅仅须要给其第二个參数指定超时的时候就可以,同Dispatch Group的dispatch_group_wait函数类似,能够通过返回值来推断。

这个函数也须要注意。假设是在OS X 10.8或iOS 6以及之后版本号中使用,Dispatch Semaphore将会由ARC自己主动管理。假设是在此之前的版本号,须要自己手动释放。


dispatch_once

dispatch_once函数通经常使用在单例模式上,它能够保证在程序执行期间某段代码仅仅执行一次,假设我们要通过dispatch_once创建一个单例类,在Swift能够这样:

class SingletonObject {

class var sharedInstance : SingletonObject {

struct Static {

static var instance : SingletonObject? = nil

}

dispatch_once(&Static.onceToken) {

Static.instance = SingletonObject()

}

return Static.instance!

}

}

这样就能通过GCD的安全机制保证这段代码仅仅运行一次。


最新文章

  1. 换个角度理解云计算之HDFS
  2. 降低磁盘IO使Oracle性能优化(转)
  3. 有关C#标签Attribute的熟悉
  4. 【CodeForces 602B】G - 一般水的题2-Approximating a Constant Range
  5. log4j的简单应用(转载)
  6. scala学习笔记(1):基本语法与容器
  7. Bugzilla+MySql+IIS+ActivePerl搭建指南
  8. HDU 5617 Jam's maze 巧妙DP
  9. Codeforces Round #238 (Div. 1)
  10. Tornado基本使用
  11. php 代码重用
  12. hdu 4857 逃生 拓扑排序+PQ,剥层分析
  13. 半个月学习的it内容
  14. 【java设计模式】【结构模式Structural Pattern】装饰模式Decorator Pattern
  15. 【BZOJ1087】【SCOI2005】互不侵犯(状态压缩,动态规划)
  16. Thread 调用方法的方式
  17. PHP 单例模式优点意义及如何实现
  18. BZOJ 2243 染色
  19. Android为TV端助力 Service 两种启动方式的区别
  20. mysql慢查询,死锁解决方案

热门文章

  1. Timus 1329. Galactic History。LCA最近公共祖先或dfs递归离线处理!
  2. 硅谷和国内的 iOS 开发到底有何不同?
  3. 【Luogu】P2774方格取数问题(最大点权独立集)
  4. HDU——2064汉诺塔III
  5. POJ——1195Mobile phones(二维树状数组点修改矩阵查询)
  6. 广东工业大学2016校赛决赛重现——E积木积水(方法据说很多)
  7. 使用 SOAPUI 测试Web Service
  8. 学习orm框架及一些看法
  9. 96. Unique Binary Search Trees(I 和 II)
  10. linux jenkins安装(四)