Dispatch 方法详解

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 queue
dispatch_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 queue
dispatch_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!"); 
}); 

 

posted @ 2014-10-22 16:39  yybz  阅读(18280)  评论(0编辑  收藏  举报