GCD概述
什么是GCD?
GCD(Grand Center Dispatch)是异步执行任务的技术之一。开发者定义想执行的任务并且追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
多线程编程
一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,所以通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。就像下图一样: ==这里所说的“一个CPU执行的CPU命令列为一条无分叉路径”就是线程。==同时存在多条这样的路径就称为“多线程”。 但是多线程编程时会出现很多问题,使用GCD大大简化了偏于复杂的多线程编程的源代码,并且可以避免多线程的一些缺点。
GCD的API
Dispatch Queue
上面我们说到GCD是:开发者定义想执行的任务并且追加到适当的Dispatch Queue中。这句话可以用下面这段源代码表示:
dispatch_async (queue, ^{
}
该源代码使用Block语法“定义想执行的任务”,通过dispatch_async 函数“追加”赋值在变量queue的“Dispatch Queue ”中。仅这样就可使指定的Block在另一线程中执行。
Dispatch Queue是什么呢?是执行处理的等待队列,按照追加任务的顺序(先进先出)执行处理。如下图: 在执行处理时,存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue ,另一种是不等待现在执行中处理的Concurrent Dispatch Queue 。二者执行情况如下图: 二者和线程的关系: 在执行Concurrent Dispatch Queue 中执行处理时,执行顺序会根据处理内容和系统状态发生改变。而Serial Dispatch Queue 则顺序固定。
既然知道了有两种Dispatch Queue ,那么如何去创建呢?有以下两种方法:dispatch_queue_create 和获取系统标准提供的Dispatch Queue 。
dispatch_queue_create
第一种方法是通过GCD的API生成Dispatch Queue ,通过dispatch_queue_create 函数可生成Dispatch Queue :
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
参数列表: 第一个参数指定Serial Dispatch Queue 的名称,命名推荐使用应用程序ID这种逆序全程域名。 第二个参数:生成Serial Dispatch Queue 类时,则指定为NULL,生成Concurrent Dispatch Queue 时,指定为DISPATCH_QUEUE_CONCURRENT 。 说明:使用dispatch_queue_create 函数可以生成任意多个Dispatch Queue ,如果过多使用会消耗大量内存。但是使用多个Serial Dispatch Queue 可以避免多线程编程引起的多个线程对数据的竞争: dispatch_queue_create 的返回值为dispatch_queue_t 类型。
生成的Dispatch Queue 必须由程序员手动释放。
dispatch_release(queue);
有release,则可推断出相应地存在dispatch_retain函数,通过这两个函数的引用计数来管理内存。
这里立即通过dispatch_release 释放是没有问题的,原因:在dispatch_async 中追加Block到Dispatch Queue ,也就是说,该Block通过dispatch_retain 函数持有Dispatch Queue ,即使立即释放Dispatch Queue ,该Dispatch Queue 由于被Block所持有也不会被释放,一旦Block执行结束后,这时谁都不再持有Dispatch Queue ,就通过dispatch_release函数释放该block持有的Dispatch Queue 。
Main Dispatch Queue/Global Dispatch Queue
第二种方法就是获取系统标准提供的Dispatch Queue 。那就是Main Dispatch Queue 和Global Dispatch Queue 。
Main Dispatch Queue 是在主线程中执行的Dispatch Queue ,因主线程只有一个,所以Main Dispatch Queue 就是Serial Dispatch Queue 。追加到Main Dispatch Queue 中的处理是在主线程的RunLoop 中执行的,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue 使用。 另一个Global Dispatch Queue 是所有应用程序都能够使用的Concurrent Dispatch Queue 。其有四个优先级:高优先级、默认优先级、低优先级和后台优先级。 关于Dispatch Queue 总结如下: 各种Dispatch Queue 的获取方法:
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
dispatch queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY HIGH, 0);
dispatch queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch queue_t globalDispatchQueueLow = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch queue t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0) ;
注意:对于Main Dispatch Queue 和Global Dispatch Queue 函数使用dispatch_retain和dispatch_release函数不会引起任何变化。
下面是使用Main Dispatch Queue 和Global Dispatch Queue 的源代码:
Fahaxiki:
[图片]
Fahaxiki:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT,O),^{
dispatch_async (dispatch_get_main_queue(),^{
}) ;
}) ;
dispatch_set_target_queue
由dispatch_queue_t_create 函数生成的Dispatch Queue ,都使用与默认与默认优先级Global Dispatch Queue 相同执行优先级的线程。可以使用 dispatch_set_target_queue 函数变更执行优先级。 该函数一共有两个参数: 第一个是要变更执行优先级的Dispatch Queue 。但是不可以是系统提供的Main Dispatch Queue 和Global Dispatch Queue 。 第二个是要使用的执行优先级相同优先级的Dispatch Queue 。
使用dispatch_set_target_queue ,还可以改变Dispatch Queue 的执行阶层,比如:有多个Serial Dispatch Queue ,若dispatch_set_target_queue 的指定目标为其中某一个,则那么原先本来应该并行执行多个Serial Dispatch Queue ,在目标Serial Dispatch Queue 上只能同时执行一个处理;所以使用此函数可以预防处理的并行执行。
dispatch_after
想在指定时间后执行处理的情况,可使用dispatch_after来实现。 比如在3秒后将指定的Block追加到Main Dispatch Queue中的源代码:
dispatch_time_t time =dispatch_time(DISPATCH_TIME_NOW, 3u11 * NSEC_PER_SEC);
dispatch after(time, dispatch_get_main_queue(),^{
NSLog(@"waited at least three seconds.");
}) ;
注意:这不是指在3秒后执行处理,而是指在3秒后追加处理到Dispatch Queue 。
参数列表:
- 第一个:是指定时间用的dispatch_time_t类型的值,该值使用
dispatch_time 函数或者dispatch_walltime 函数作成。 - 第二个:指定要追加处理的
Dispatch Queue 。 - 第三个:指定记述要处理的
Block 。
Dispatch Group
dispatch group 由dispatch_group_create 函数生成的dispatch_group_t 类型的。使用结束需要通过dispatch_release 释放。 下面举例介绍一下dispatch group的用法:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,O);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue:^{NSLog(@"blk0");});
dispatch_group_async(group,queue:^{NSLog(@"blk1");}); dispatch_group_async(group,queue:^{NSLog(@"blk2");});
dispatch_group_notify(group,
dispatch_get_main_queue():^{NSLog(@"done");}); dispatch_release(group);
在Dispatch Group中也可以使用dispatch_group_wait 函数仅等待全部执行结束:
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
第二个参数指定为等待的时间,它属于dispatch_time_t 类型的值。
dispatch_barrier_async
在访问数据库或者文件时,使用Serial Dispatch Queue 可避免数据竞争的问题。
为了高效的进行访问,可以将读取处理追加到Concurrent Dispatch Queue 中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue 中即可(在写入处理结束之前,读取处理不可执行)。 dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue 上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue 上。然后在由dispatch_barrier_async 函数追加的处理执行完毕之后,Concurrent Dispatch Queue 才恢复一般的动作。 dispatch_barrier_async 的使用方法特别简单,用它代替dispatch_async ,它与dispatch_async 使用方法是一样的。
dispatch_sync
dispatch_async 函数的“async”意味着“非同步”,就是将指定的Block“非同步”地追加到指定的Dispatch Queue中。dispatch_async 函数不做任何等待。 相反dispatch_sync 函数,它意味着“同步”,也就是将指定的Block“同步”追加到指定的Dispatch Queue中。在追加结束之前,dispatch_sync 函数会一直等待。 (图片中为dispatch_sync)。
一旦调用dispatch_sync 函数,那么在指定的Block处理执行结束之前,该函数不会返回。所以极易造成死锁。比如在主线程中调用该函数等。
由dispatch_barrier_async 函数中含有async 可推测出,相应地也有dispatch_barrier_sync 函数。dispatch_barrier_sync 函数的作用是在等待追加的处理全部执行结束后,再追加处理到Dispatch Queue中,此外,它还会等待追加处理的执行结束。
dispatch_apply
dispatch_apply 函数是dispatch_sync 函数和Dispatch Group 的关联API,该函数按照指定的次数将指定的Block追加到指定的Dispatch Queue ,并等待全部处理执行结束。
dispatch_apply(n, queue, ^() {
});
参数列表:
- n为指定次数
- queue为指定Dispatch Queue
- Block为指定处理
dispatch_suspend / dispatch_resume
dispatch_suspend 函数用于挂起不希望执行的Dispatch Queue :
dispatch_suspend(queue);
dispatch_resume 函数用于恢复被挂起的Dispatch Queue :
dispatch_resume(queue)
Dispatch Semaphore
Dispatch Semaphore 是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数为1或者大于1时,计数减一而不等待。 使用方法: 使用dispatch_semaphore_create 函数生成dispatch_semaphore_t 类型的Dispatch Semaphore 。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
参数表示的是计数的初始值。 该函数课通过dispatch_retain 持有,通过dispatch_release 释放。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait 函数等待Dispatch Semaphore 的计数值达到1或者等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait 函数返回。可通过函数返回值进行达到一些希望的目的和操作。
dispatch_time_t time =dispatch_time( DISPATCH_TIME_NOW, 1U11 * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if(result ==0){
} else {
}
dispatch_semaphore_wait 函数返回0时,可安全地执行需要进行排他控制的处理。该处理结束时通过dispatch_semaphore_signal 函数将Dispatch Semaphore 的计数值加1。
dispatch_once
dispatch_once 函数是保证在应用程序执行中只执行一次指定处理的API。在之前我写的博客Manage封装一个网络请求中使用的就是这个函数来实现的。
static dispatch_once_t pred;
dispatch_once(&pred,^{
));
单例模式就是通过此函数来实现的。
Dispatch I/O
在读取较大文件时,如果将其分成合适大小并使用Global Dispatch Queue 并列读取的话,速度会快不少。实现这一功能的就是Dispatch I/O 和Dispatch Data 。
GCD的实现
Dispatch Queue
GCD的Dispatch Queue 非常方便,如何实现它呢? 首先确认一下实现Dispatch Queue 而使用的软件组件:
Dispatch Source
Dispatch Source是BSD系内核惯有功能kqueue的包装。kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。其CPU符合非常小,尽量不占用资源。 Dispatch Source可处理以下事件:
Dispatch Source与Dispatch Queue的不同是,后者可以将追加的执行处理取消,而前者则不能。
|