Dispatch_queue
dispatch_queue是一种执行处理的等待对列。按照追加顺序(FIFO)执行处理。
dispatch_queue分为两种,一种是等待当前正在处理的任务完成后再执行下一个任务,每次只执行一个任务,按 照顺序执行,称为Serial Dispatch Queue,另一种就是不等待,意思就是不管当前的任务是否执行完毕都开始执 行下一个任务,任务并发执行,称为Concurrent Dispatch Queue.
当变量queue为Concurrent Dispatch Queue时,虽然不用等待处理结束,就可以并行执行多个任务。但并行执行 的处理数量取决于当前的系统的状态。系统只生成所需的线程执行处理,处理结束后,系统会结束不需要的线程。
两种形式的queue根据用户需求来定义,如果希望按顺序执行,那么就创建Serial Dispatch Queue,如果希望并行 执行,并且执行顺序无关重要,那么就可以使用Concurrent Dispatch Queue。
/////第一个参数为queue的名称,命名规则为FQDN,应用名称ID的倒序+queue名字////第二个参数为Null 创建Serial dispatch queue; 如果为DISPATCH_QUEUE_CONCURRENT 则创建的是Concurrent queue;//串行队列dispatch_queue_t mySerialQueue = dispatch_queue_create("com.cnblogs.yybz.gcd.serialQueue",NULL);dispatch_async(mySerialQueue, ^{ NSLog(@"hello GCD");}); //并行队列dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.cnblogs.yybz.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(myConcurrentQueue,^{ NSLog(@"hello");});
使用dispatch_queue_create创建Serial Dispatch Queue,该queue虽然每次只执行一个任务,但是通过 dispatch_queue_create 可以创建多个Serial Dispatch Queue,将处理追加到多个queue中,每次就同时执行多 个任务,系统对于一个Serial Dispatch Queue就生成一个线程,如果这样的线程过多,对资源耗费是相当大的, 反而降低了系统的性能,因此需要注意创建的数量。
对于共享数据的操作,应该使用Serial Dispatch Queue,这样不会造成数据竞争,生成脏数据。生成的Serial Dispatch Queue个数仅当所必需的数量。例如数据库创建表需要一个,文件写入需要一个,切忌不能大量创建。
当数据不发生数据竞争的时候可以使用Concurrent Dispatch Queue,对于该queue,不管生成多少线程,都受系 统限制,系统会回收不用的线程,相对于Serial Dispatch Queue 问题要少。
Main Dispatch Queue/Global Dispatch Queue
通过dispatch_queue_create() 函数可以得到我们想要的queue,其实不用特意去创建Dispatch Queue,系统已经为我们实现了几个,一个是Main Dispatch Queue 一个是Global Dispatch Queue。
Main Dispatch Queue
将任务放在主线程中去执行,主要执行UI操作和UIKit操作(此类操作必须放到主线程中执行),这个和NSObject类提供的performSelectorOnMainThread方法执行的效果一样。
Global Dispatch Queue
是所有应用程序都能够使用的Concurrent Dispatch Queue。不用刻意的去创建一个 Concurrent 的Queue。只要获得系统的这个即可。
追加到Global Dispatch Queue中的线程可以设置优先级,优先级分为四种,
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
Dispatch_set_target_queue
通过dispatch_queue_create函数创建的queue,其优先级的线程和采用默认优先级的Global dispatch queue的线 程是相同的,那么如何改变通过dispatch_queue_create创建queue的优先级呢?可以通过 dispatch_set_target_queue这个函数该实现。
/////dispatch_queue_t myQueue = dispatch_queue_create("com.cnblogs.yybz", NULL); dispatch_queue_t globalHightQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);dispatch_set_target_queue(myQueue, globalHighQueue);/** 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。 */
注意:所有的用户队列都有一个目标队列概念。从本质上讲,一个用户队列实际上是不执行任何任务的,但是它会 将任务传递给它的目标队列来执行。通常,目标队列是默认优先级的全局队列。
用户队列的目标队列可以用函数 dispatch_set_target_queue来修改。我们可以将任意dispatch queue传递给这个 函数,甚至可以是另一个用户队列,只要别构成循环就行。这个函数可以用来设定用户队列的优先级。比如我们可 以将用户队列的目标队列设定为低优先级的全局队列,那么我们的用户队列中的任务都会以低优先级执行。高优先 级也是一样道理。
有一个用途,是将用户队列的目标定为main queue。这会导致所有提交到该用户队列的block在主线程中执行。这 样做来替代直接在主线程中执行代码的好处在于,我们的用户队列可以单独地被挂起和恢复,还可以被重定目标至 一个全局队列,然后所有的block会变成在全局队列上执行(只要你确保你的代码离开主线程不会有问题)。
还有一个用途,是将一个用户队列的目标队列指定为另一个用户队列。这样做可以强制多个队列相互协调地串行执行,这样足以构建一组队列,通过挂起和暂停那个目标队列,我们可以挂起和暂停整个组。想象这样一个程序:它 扫描一组目录并且加载目录中的内容。为了避免磁盘竞争,我们要确定在同一个物理磁盘上同时只有一个文件加载 任务在执行。而希望可以同时从不同的物理磁盘上读取多个文件。要实现这个,我们要做的就是创建一个dispatch queue结构,该结构为磁盘结构的镜像。
首先,我们会扫描系统并找到各个磁盘,为每个磁盘创建一个用户队列。然后扫描文件系统,并为每个文件系统创 建一个用户队列,将这些用户队列的目标队列指向合适的磁盘用户队列。最后,每个目录扫描器有自己的队列,其 目标队列指向目录所在的文件系统的队列。目录扫描器枚举自己的目录并为每个文件向自己的队列提交一个 block。由于整个系统的建立方式,就使得每个物理磁盘被串行访问,而多个物理磁盘被并行访问。除了队列初始 化过程,我们根本不需要手动干预什么东西。
Dispatch_after
有时候我们需要延迟执行某个动作,比如三秒后打印一句话,可以调用[self performSelector:nil withObject:nil afterDelay:3],来实现任务延迟,也可以使用dispatch_after来完成这个动作:
////int64_t delayInSeconds = 10.0;/**@parameter 1,时间参照,从此刻开始计时*@parameter 2,延时多久,此处为秒级,还有纳秒等。10ull * NSEC_PER_MSEC */dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"hello world!");});/*需要注意的是,使用dispatch_after实现延迟执行某动作,时间并不是很精确,实际上是过多久将Block追加到 main Queue中,而不是执行该动作,如果此时main queue中的任务很多,没有执行完毕,那么新添加的这个动 作就要继续推迟。*/NSLog(@"hello world");dispatch_async(dispatch_get_main_queue(), ^{ sleep(10); NSLog(@"sleep 10s");});NSLog(@"hello objective-c");dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC);dispatch_after(delayTime,dispatch_get_main_queue(),^(void){ NSLog(@"after 5s , execute!");});NSLog(@"hello yybz");// 2014-08-12 14:02:53.220 GCD[3273:11303] hello world// 2014-08-12 14:02:53.221 GCD[3273:11303] hello objective-c // 2014-08-12 14:02:53.222 GCD[3273:11303] hello yybz// 2014-08-12 14:03:03.229 GCD[3273:11303] sleep 10s// 2014-08-12 14:03:08.230 GCD[3273:11303] after 5s , execute!
如果对时间的精确度没有高要求,只是为了推迟执行,那么使用dispatch_after还是很不错的。dispatch_time_t类 型的时间我们可以通过dispatch_time来创建,也可以通过dispatch_walltime来创建。前者创建的时间多以第一个参数为参照物,之后过多久执行任务。后者多用于创建绝对时间,如某年某月某日某时某分执行某任务,比如闹钟的设置。
Dispatch Group
有时候我们需要某些任务执行完毕后再去执行另一个任务,可能最后这个任务需要前几个任务的数据,必须等前几 个任务完毕,在执行自己。当然创建一个serial queue,按顺序执行任务,最后执行剩下的那个任务完全没有问 题。
但如果我们创建的是concurrent queue呢,管理起来就显得很复杂,GCD提供了一个Dispatch Group方法,可以 将并行执行的任务追加到一个组中,程序会监控这个组中的任务,等待所有任务完成后通过 dispatch_group_notify将剩下的任务追加到指定的queue中,就这么简单。
// ///获得一个globle queuedispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); ///创建一个组dispatch_group_t group = dispatch_group_create();///追加任务到组中dispatch_group_async(group,queue, ^{ sleep(5); NSLog(@"hello world");});dispatch_group_async(group,queue,^{ sleep(1); NSLog(@"hello yybz");});dispatch_group_async(group, queue,^{ sleep(3); NSLog(@"hello objective-c");});///当任务结束后,会将最后一个任务追加到指定的queue,这里是main queuedispatch_group_notify(group,dispatch_get_main_queue(),^{ NSLog(@"done");});//2014-08-19 09:10:21.991 GCDTest[6739:3503] hello yybz//2014-08-19 09:10:23.991 GCDTest[6739:3703] hello objective-c//2014-08-19 09:10:25.990 GCDTest[6739:1803] hello world//2014-08-19 09:10:25.994 GCDTest[6739:60b] done
也可以利用dispatch_group_enter和dispatch_group_leave手动管理block的运行状态,等价于 dispatch_group_async,但必须成对出现,且进入退出次数必须匹配
dispatch_group_enter(group); dispatch_async(queue,^{ //code dispatch_group_leave(group); });
Dispatch_apply
dispatch_apply 是同步函数,会阻塞当前线程直到所有循环迭代执行完成。当提交到并发queue时,循环迭代的执 行顺序是不确定的。
/dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); __block int sum = 0; __block int pArray = 3; dispatch_apply(5,queue,^(size_t i) { sum += pArray; NSLog(@">> Current Sum: %d",sum);});NSLog(@" >> sum: %d", sum); /*#### Output ####*///2014-01-04 21:42:35.248 MultiThreading1[4505:1703] >> Current Sum: 6//2014-01-04 21:42:35.248 MultiThreading1[4505:1903] >> Current Sum: 12//2014-01-04 21:42:35.248 MultiThreading1[4505:1803] >> Current Sum: 9//2014-01-04 21:42:35.248 MultiThreading1[4505:707] >> Current Sum: 3//2014-01-04 21:42:35.252 MultiThreading1[4505:1703] >> Current Sum: 15//2014-01-04 21:42:35.253 MultiThreading1[4505:707] >> sum: 15
Dispatch_barrier_async
函数的作用:如果任务是通过dispatch_barrier_async函数追加到concurrent queue中的,执行该任务之前,等待 上一个任务执行完毕,执行该任务时,其他的线程不执行,直到该任务完成,才恢复执行剩余的任务。
多线程对数据库和文件读写的操作,多个写操作不能同时出现针对一个表的操作,这样可能造成脏数据,发生意想 不到的错误,我们可以使用serial queue 来避免。但是多个读操作可以并行执行,这样可以提高效率。有时我们希 望读取的时候又来更新数据,后续读取的数据将是更新后的数据。如果只是简单的将这一系列的任务添加到 concurrent queue中,那么就会出现脏数据,如何避免呢,就是用dispatch_barrier_async来操作:
///dispatch_queue_t queue = dispatch_queue_create("com.cnblogs.yybz", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue,^{ sleep(5); NSLog(@"1 reading");});dispatch_async(queue, ^{ sleep(2); NSLog(@"2 reading");});// 简单的添加// dispatch_async(queue,^{// NSLog(@"writing");// });dispatch_barrier_async(queue,^{ NSLog(@"writing***");});dispatch_async(queue, ^{sleep(1); NSLog(@"3 reading");});dispatch_async(queue,^{ NSLog(@"4 reading");});//**********************************************************// 简单的添加,可能读取的数据有误// 2014-08-19 10:01:18.946 GCDTest[6794:3703] writing// 2014-08-19 10:01:18.984 GCDTest[6794:3d03] 4 reading // 2014-08-19 10:01:19.971 GCDTest[6794:3703] 3 reading // 2014-08-19 10:01:20.947 GCDTest[6794:3503] 2 reading // 2014-08-19 10:01:23.947 GCDTest[6794:1803] 1 reading//**********************************************************// 使用dispatch_barrier_async添加的任务,在其之后添加的任务将等待他执行完成,才会执行剩余的// 2014-08-19 10:05:36.128 GCDTest[6812:3503] 2 reading// 2014-08-19 10:05:39.128 GCDTest[6812:1803] 1 reading// 2014-08-19 10:05:39.132 GCDTest[6812:1803] writing*** // 2014-08-19 10:05:39.142 GCDTest[6812:3503] 4 reading // 2014-08-19 10:05:40.142 GCDTest[6812:1803] 3 reading
Dispatch_sync
前面都是通过dispatch_async函数追加block到queue中,意味着异步添加,与异步对应的就是同步追加。同步追加任务意味着当前的线程要停止。什么情况下会用到同步呢?例如:执行main queue时, 需要另外的globle queue中处理完的数据,此时就可以使用同步dispatch_sync:
////dispatch_queue_t queue = dispatch_get_global_queue(0, 0);///同步追加的block 执行完毕后,函数才会返回。否则该线程一直等待。dispatch_sync(queue,^{ sleep(5); NSLog(@"done!"); });