Grand Central Dispath(GCD):是iOS 4和OS X Snow Leopard 开始引入的新多线程编程功能。
GCD是异步执行任务的技术之一。
我们只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
这里的线程管理是作为系统的一部分来实现的。
在GCD出现之前,Cocoa框架在NSObject类中提供了 performSelectorInBackground: withObject:方法和 performSelectorOnMainThread: withObject: waitUntilDone:方法等这样简单的多线程技术。
而GCD出现后,我们就可以通过 dispatch_async(queue, ^{ });和 dispatch_async(dispatch_get_main_queue(), ^{ });在后台和主线程中完成相应操作。
相比之下,GCD代码更为简洁,并且由于是系统管理线程,执行效率更高。
一个CPU核一次只能执行一个命令。CPU核执行的命令列也是一条无分叉的路径,即使地址分散在各处。
这种无分叉路径不只1条,存在多条时即为“多线程”。
多线程可能是一个CPU核在多个线程之间来回切换,看上去像一个CPU核并列地执行多个线程一样。也有可能是真的具有多个CPU核并行执行多个线程。
多线程可能发生的问题:
1、多个线程同时更新相同的资源,导致数据竞争;
2、多个线程相互等待,形成死锁;
3、使用太多线程会消耗大量内存;
但是多线程也必须使用,因为多线程可以保证应用程序的响应性能。但是长时间的处理不能在主线程中执行,需在其他线程中执行。因为在主线程中长时间处理会妨碍主循环RunLoop的执行,从而导致不能更新用户界面,应用程序的画面长时间停滞等问题。
“定义想执行的任务并追加到适当的Dispatch Queue中”。其中,Dispatch Queue就是执行处理任务的等待队列,而Block中就是待执行的任务。
在执行处理时,会用到两种队列:
1、Serial Dispatch Queue: 等待前一个任务执行完毕再开始处理下一个任务;
2、Concurrent Dispatch Queue: 不等待前一个任务处理完就开始执行下一个任务;
关于串行/并行,同步/异步的理解:
异步串行:就是另起一个新的线程,在这个新线程中,按照任务加入的顺序依次执行完所有的任务;
异步并行:就是创建多个新的线程,同时执行多个任务,因此,这个方式执行完任务的顺序是随机的;
同步并行:就是不创建新的线程,因此,即使是并行的队列,也必须按照任务添加的顺序依次执行完,才能继续后面的任务;
同步串行:和同步并行一样,不能开启新线程,只能按队列中任务的顺序来依次执行。
XNU内核(iOS和OS X的核心)决定了可以并行的线程最大数;XNU内核通过Concurrent Dispatch Queue来管理并执行多线程。
1、通过 GCD的API:dispatch_queue_create生成Dispatch Queue。
2、获取系统标准提供的Dispatch Queue;
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
其中,label表示queue的名称,通常使用应用程序ID的逆序全称域名来表示。这样也方便程序在出现crash时,方便定位和调试。
第二个参数,attr用来指明要生成的队列类型,NULL和DISPATCH_QUEUE_SERIAL都表示生成串行队列,DISPATCH_QUEUE_CONCURRENT表示生成并行队列。
dispatch_queue_create函数可以生成任意个Dispatch Queue。
当生成多个串行Dispatch Queue时,各个串行队列分别对应生成自己的线程,来并行执行,但是每个串行队列同时只能执行一个处理。
如果生成过多的串行队列,就会生成多个线程,因此会消耗大量的内存,引起大量的上下文切换,大幅降低系统的响应性能。
因此只在避免多线程更新相同资源导致的数据竞争问题时,使用Serial Dispatch Queue。因此Serial Dispatch Queue生成的数量仅需所需要的数量。
在不存在数据竞争想并行处理问题时,可以使用Concurrent Dispatch Queue。对于并行队列,不管生成多少,XNU内核只使用有效管理的线程,因此不会出现串行队列大量消耗内存的问题。
因为Dispatch Queue的dispatch_queue_t类型,不是OC对象,不能自动管理其内存,因此通过dispatch_queue_create函数生成的Dispatch Queue需要我们通过dispatch_retain和dispatch_release函数来管理内存。在ARC中不需要使用dispatch_retain和dispatch_release函数。
即使在 dispatch_async后立即通过dispatch_release释放dispatch_queue_create创建的队列,也不会有任何问题。
因为,在dispatch_async函数中追加Block到Dispatch Queue中后,该Block就通过dispatch_retain函数持有了Dispatch Queue,因此,Dispatch Queue不会被立即废弃,Block也可以执行,当Block执行结束后,这时谁都不持有Dispatch Queue,Dispatch Queue被废弃了,因此,会通过dispatch_release函数释放掉Dispatch Queue。
在其他的GCD的API中,名称中含有“create”的API生成的对象,都有必要通过dispatch_retain函数持有,和dispatch_release函数在不需要时释放。在ARC中不需要使用dispatch_retain和dispatch_release函数。
通过获取系统提供的Main Dispatch Queue 和Global Dispatch Queue。
Main Dispatch Queue是主线程队列,是一个串行队列。用来执行一些界面更新之类的处理。
Global Dispatch Queue是全局队列,是一个并行队列。Global Dispatch Queue分为四个优先级,分别是:高优先级,默认优先级,低优先级,后台优先级。
系统队列获取的方法:
//Main Dispatch Queue
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
//高优先级Global Dispatch Queue
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//默认优先级Global Dispatch Queue
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//低优先级Global Dispatch Queue
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//后台优先级Global Dispatch Queue
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
对Main Dispatch Queue 和Global Dispatch Queue使用dispatch_retain和dispatch_release函数不会产生任何作用。在ARC中不需要使用dispatch_retain和dispatch_release函数。
使用dispatch_queue_create生成的队列,不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都是和默认优先级的Global Dispatch Queue相同执行优先级的。
如果需要变更队列的优先级,就需要使用 dispatch_set_target_queue(<#dispatch_object_t _Nonnull object#>, <#dispatch_queue_t _Nullable queue#>)。
其中,第一个参数:是待变更优先级的队列。
第二个参数是:要使用的优先级的Global Dispatch Queue。
但是,待变更的队列不能是Main Dispatch Queue 和Global Dispatch Queue,因为不知道会发生什么状况。
并行——>串行:变更执行阶层。
如果多个Serial Dispatch Queue(Queue1,Queue2,Queue3…)都使用dispatch_set_target_queue函数指定目标为一个新的Serial Dispatch Queue(Queue A),那么原先本应并行执行的多个Serial Dispatch Queue(Queue1,Queue2,Queue3…),在目标Serial Dispatch Queue(Queue A)上,就会串行执行,也就是,同时只能执行一个处理。
这种变更执行阶层,在某些情况下可以防止并行处理。
1、 dispatch_after表示在指定时间后执行处理。但可能不是严格按照指定时间执行,而是稍有延迟。
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
第一个参数:when是指定时间的dispatch_time_t类型;
第二个参数是:要追加处理的队列;
第三个参数是:要处理的Block;
dispatch_time_t类型的时间可以通过dispatch_time或 dispatch_walltime获得。
dispatch_time是通过计算相对时间获取时间;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 200ull * NSEC_PER_SEC);
表示获取从现在起200毫秒以后的时间。
dispatch_walltime是通过计算绝对时间获取的时间。
dispatch_walltime是从struct timespec类型获取dispatch_time_t时间类型;
如果我们想在Dispatch Queue中的多个处理全部执行完成时,做相应的结束处理。
对于Serial Dispatch Queue来说,我们只需要将结束处理追加在队列的最后就可以了。
但是对于Concurrent Dispatch Queue来说,每个任务执行完成的时机是不确定的,因此追加最后的结束处理也比较困难。对于这种情况,我们就可以使用Dispatch Group。
demo如下:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{NSLog(@"1");});
dispatch_group_async(group, queue, ^{NSLog(@"2");});
dispatch_group_async(group, queue, ^{NSLog(@"3");});
dispatch_group_notify(group, queue, ^{NSLog(@"4");});
同一段代码,多打印几次日志,
可以发现:
前三个Block输出的日志顺序是不确定的,但是最后一个block的输出次序一定是在最后,也就是队列中的前三个Block执行完毕后再执行。
dispatch_group_async的用法与 dispatch_async函数基本相同,都可以追加Block到Dispatch Queue中,与dispatch_async不同的是,dispatch_group_async多了一个参数Dispatch Group,并且指定的Block是属于对应的Dispatch Group的。
在Dispatch Group中也可以使用 dispatch_group_wait函数来在指定时间结束或全部处理结束时执行。
dispatch_group_wait(<#dispatch_group_t _Nonnull group#>, <#dispatch_time_t timeout#>)
中,第一个参数就是要监测的group,第二个参数就是需等待的dispatch_time_t时间类型。
dispatch_group_wait函数一旦调用,就会处于调用而不返回状态,因此,调用其的线程就会处于停止状态,只要等group执行结束或者指定时间到达时,才会返回。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 200ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0)
{
//返回值result为0时,表示经过指定时间time后,Dispatch Group中的处理依然没有执行结束。
}
else
{
//返回值result不为0时,表示Dispatch Group中的处理已经全部执行结束。
}
返回值result不为0时,表示经过指定时间time后,Dispatch Group中的处理依然没有执行结束。返回值result为0时,表示Dispatch Group中的处理已经全部执行结束。
dispatch_barrier_async函数结合 dispatch_queue_create函数生成的Concurrent Dispatch Queue一起,可以使追加在dispatch_barrier_async之前的处理全部执行结束后,再将dispatch_barrier_async中追加在Queue里的处理执行。dispatch_barrier_async中的处理执行完毕后,再恢复dispatch_async的一般动作,执行dispatch_barrier_async之后所追加的处理。
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);
dispatch_barrier_async(queue, block4);
dispatch_async(queue, block5);
dispatch_async(queue, block6);
dispatch_async(queue, block7);
使用dispatch_barrier_async函数和 dispatch_queue_create函数生成的Concurrent Dispatch Queue可以实现高效率的安全的数据库访问和文件访问。
dispatch_sync是等待处理执行结束后,再继续。
一旦调用,在指定处理结束之前,该函数不会返回。效果等同于dispatch_group_wait函数。
dispatch_sync和主线程队列Main Dispatch Queue,以及Serial Dispatch Queue一起使用时,很容易形成死锁。需谨慎使用。
比如,下面的和Main Dispatch Queue一起使用:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"hello");});
});
NSLog(@"what");
在执行这几行代码时,会在第三行代码处,报Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)死锁的错误。因此最后一行代码时执行不到的。
同样的,在和Serial Dispatch Queue一起使用时,也容易发生类似问题:
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"hello");});
});
NSLog(@"what");
在执行这几行代码时,会在第三行代码处,报Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)死锁的错误。因此最后一行代码时执行不到的。
dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t _Nonnull queue#>, <#^(size_t)block#>)
第一个参数iterations:重复的次数;
第二个参数queue:要追加的队列;
第三个参数block:要追加的处理;
demo如下:
NSArray *array = [NSArray arrayWithObjects:@0, @1, @2, @3, nil];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index){
NSLog(@"%@",[array objectAtIndex:index]);
});
根据打印的日志
可以看出, dispatch_apply是非同步的。但是 dispatch_apply会等待追加的处理全部执行完毕。
挂起指定队列:
dispatch_suspend(queue);
恢复指定队列:
dispatch_resume(queue);
dispatch_semaphore_wait(<#dispatch_semaphore_t _Nonnull dsema#>, <#dispatch_time_t timeout#>)
是比Serial Dispatch Queue和dispatch_barrier_async更细粒度的避免数据竞争的排他控制方法。
dispatch_semaphore_wait和dispatch_group_wait一样,可以设定等待时间,当指定时间到达时,或者Dispatch Semaphore的计数值大于等于1时返回。返回时会将Dispatch Semaphore的计数值减1。
当Dispatch Semaphore的计数值大于等于1时或者在指定等待时间内Dispatch Semaphore的计数值大于等于1时,返回值为0。当指定时间到时,Dispatch Semaphore的计数值为0的话,返回值不为0。
当dispatch_semaphore_wait返回值为0时,就可安全的执行排他处理。处理结束后,使用 dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1。
dispatch_once函数保证程序中只执行一次处理的函数。通常用在生成单例对象时。
使用GCD要比使用NSThread和pthreads这些一般的多线程编程API更好。因为任何由程序员编写的管理线程的代码,在性能方面都比不上iOS和OS X的核心XNU内核级实现的GCD。因此我们可以尽量多使用GCD或者使用Cocoa框架中GCD的NSOperation类的API。