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!");
}); 
时间: 2024-08-04 14:55:19

Dispatch 方法详解的相关文章

JavaScript原生对象属性和方法详解——Array对象 转载

length 设置或返回 数组中元素的数目. 注意:设置 length 属性可改变数组的大小.如果设置的值比其当前值小,数组将被截断,其尾部的元素将丢失.如果设置的值比它的当前值大,数组将增大,新的元素被添加到数组的尾部,它们的值为 undefined.所以length不一定代表数组的元素个数. var arr = new Array(3) arr[0] = "John" arr[1] = "Andy" arr[2] = "Wendy" cons

Python数据类型及其方法详解

Python数据类型及其方法详解 我们在学习编程语言的时候,都会遇到数据类型,这种看着很基础也不显眼的东西,却是很重要,本文介绍了python的数据类型,并就每种数据类型的方法作出了详细的描述,可供知识回顾. 一.整型和长整型 整型:数据是不包含小数部分的数值型数据,比如我们所说的1.2.3.4.122,其type为"int" 长整型:也是一种数字型数据,但是一般数字很大,其type为"long" 在python2中区分整型和长整型,在32位的机器上,取值范围是-2

【转】深入学习JavaScript: apply call方法 详解(转)

Js apply方法详解 原文:http://blog.csdn.net/myhahaxiao/article/details/6952321 我在一开始看到JavaScript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这里我做如下笔记,希望和大家分享..  如有什么不对的或者说法不明确的地方希望读者多多提一些意见,以便共同提高.. 主要我是要解决一下几个问题: 1.        apply和cal

hbase-0.94安装方法详解

先决条件: 1)java环境,需要安装java1.6以上版本 2)hadoop环境,由于HBase架构是基于其他文件存储系统的,因此在分布式模式下安装Hadoop是必须的,但是,如果运行在单价模式下,此条件可以省略.Hadoop-1.2.1的安装方法参考 hadoop-1.2.1安装方法详解 注意:安装时要注意Hadoop和HBase之间的版本关系,如果不匹配,很可能会影响HBase系统的稳定性. 本帖教程采用的hadoop是hadoop-1.2.1,hbase采用的是hbase-0.94 hb

oc中字典的实现方法详解

一:字典的基本概念 Foundation中的字典(NSDictionary,NSMutableDictionary)是由键-值对组成的数据集合.正如,我们在字典里查找单词的定义一样. 通过key(键),查找的对应的value(值),key通常是字符串对象,也可以是其他任意类型对象.在一个字典对象中,key的值必须是唯一的. 此外,字典对象的键和值不可以为空(nil),如果需要在字典中加入一个空值,可以加入NSNull对象 二:不可变字典-NSDictionary 1:初始化(以一个元素和多个元素

57. 数对之差的最大值:4种方法详解与总结[maximum difference of array]

[本文链接] http://www.cnblogs.com/hellogiser/p/maximum-difference-of-array.html [题目] 在数组中,数字减去它右边的数字得到一个数对之差.求所有数对之差的最大值.例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果. [分析] 看到这个题目,很多人的第一反应是找到这个数组的最大值和最小值,然后觉得最大值减去最小值就是最终的结果.这种思路忽略了题目中很重要的一点:数对之差

Java中的main()方法详解

在Java中,main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main,方法必须是public static void 类型的,方法必须接收一个字符串数组的参数等等. 在看Java中的main()方法之前,先看一个最简单的Java应用程序HelloWorld,我将通过这个例子说明Java类中main()方法的奥秘,程序的代码如下: 1 /** 2 * Java中的main()方法

查看登陆系统用户的信息的三种方法详解

查看登陆系统用户的信息的三种方法详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.who这个命令显示可以谁在登陆,但是这个有很多的花式玩法,这个命令超简单 语法:who [OPTION]... [ FILE | ARG1 ARG2 ] 1.参数:-u,显示闲置时间,若该用户在前一分钟之内有进行任何动作,将标示成"."号,如果该用户已超过24小时没有任何动作,则标示出"old"字符串. 例如: 2.参数:-m,此参数的效果和指定"a

HTTP请求方法详解

HTTP请求方法详解 请求方法:指定了客户端想对指定的资源/服务器作何种操作 下面我们介绍HTTP/1.1中可用的请求方法: [GET:获取资源]     GET方法用来请求已被URI识别的资源.指定的资源经服务器端解析后返回响应内容(也就是说,如果请求的资源是文本,那就保持原样返回:如果是CGI[通用网关接口]那样的程序,则返回经过执行后的输出结果).     最常用于向服务器查询某些信息.必要时,可以将查询字符串参数追加到URL末尾,以便将信息发送给服务器.     使用GET请求时经常会发