您的位置:首页 > 其它

GCD的使用总结

2016-03-12 16:47 267 查看
什么是GCD?

Grand Central Dispatch或者GCD,是一套底层API,提供了一种新的方法来进行并发程序编写。使用GCD来提升程序性能以及发挥多核系统优势.

GCD的工作原理:

让程序平行的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务.

GCD提供了很多超越传统多线程编程的优势.

简单易用,居于block的血统,导致它能极为简单的在不同代码作用域之间传递上下文,

效率,它在很多地方比之专门创建消耗资源的线程更实用且快速,

性能,GCD自动根据系统负载来增减线程数量.这就减少了上下文切换以及增加了计算效率.

GCD中有三种队列类型:



1 The main queue: 与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。

2 Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:高、中(默认)、低三个优先级队列。可以调用dispatch_get_global_queue函数传入优先级来访问队列。

3 用户队列: 用户队列 (GCD并不这样称呼这种队列, 但是没有一个特定的名字来形容这种队列,所以我们称其为用户队列) 是用函数 dispatch_queue_create 创建的队列.


尽管GCD是纯c语言的,但它被组建成面向对象的风格。GCD对象被称为dispatch object。Dispatch object像Cocoa对象一样是引用计数的。

有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程
同步: 在当前线程中执行任务,不具备开启新线程的能力
异步: 在新的线程中执行任务,具备开启新线程的能力


并发和串行决定了任务的执行方式
并发: 多个任务并发(同时)执行
串行: 一个任务执行完毕后,再执行下一个任务
dispatch_resume() 继续执行线程

dispatch_suspend() 挂起线程 中断线程

dispatch_sync() 同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。

dispatch_async () 异步添加进任务队列,它不会做任何等待

dispatch_get_main_queue() 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行

dispatch_queue_create() 创建一个队列 第一个参数是一个标签,这纯是为了debug。Apple建议我们使用倒置域名来命名队列,比如“com.dreamingwish.subsystem.task”。这些名字会在崩溃日志中被显示出来,也可以被调试器调用这在调试中会很有用,所有尽量不要重名了。第二个参数 设置你的队列是否串行或并行.一般我是设置NULL,它是串行

DISPATCH_QUEUE_SERIAL | DISPATCH_QUEUE_CONCURRENT

dispatch_get_global_queue() 全局并发队列,并由整个进程共享, 可设定优先级来选择高、中、低, 后台优先级队列。

说明:全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
dispatch_group_create()创建一个调度任务组 它可以将对象关联

dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);



group 提交到的任务组,这个任务组的对象会一直持续到任务组执行完毕

queue 提交到的队列,任务组里不同任务的队列可以不同

block 提交的任务



func dispatch_group_notify(_ group: dispatch_group_t!,_ queue: dispatch_queue_t!,_block: dispatch_block_t!)

参数: group监听的任务组

queue 执行完毕的这个闭包所在的队列

block执行完毕所响应的任务

dispatch_group_wait 设置等待时间,在等待时间结束后,如果还没有执行完任务组,则返回。返回0代表执行成功,非0则执行失败



dispatch_group_enter/dispatch_group_leave



func dispatch_group_enter(_ group: dispatch_group_t!)func dispatch_group_leave(_ group: dispatch_group_t!)

这两个方法显示的讲任务组中的任务未执行完毕的任务数目加减1,这种方式用在不使用dispatch_group_async来提交任务,注意:这两个[b]函数要配合使用,有enter要有leave,这样才能保证功能完整实现。也可以用这对函数来让一个闭包关联多个Groupdispatch_group_notify 用来监听任务组事件的执行完毕[/b]

void dispatch_apply(size_t iterations, dispatch_queue_t queue,void (^block)(size_t)) 这个函数调用单一block多次,并平行运算,然后等待所有运算结束,作用是把指定次数指定的block添加到queue中, 第一个参数是迭代次数,第二个是所在的队列,第三个是当前索引,

看完上面的介绍,下面来简单的使用GCD吧.

一个在异步执行的串行队列,如若想并发执行将DISPATCH_QUEUE_SERIAL换成DISPATCH_QUEUE_CONCURRENT即可.

串行队列只会创建一条异步线程,并发队列将会创建多个线程

array = @[@"guo",@"bin",@"ai",@"chun",@"yan"];
    dispatch_queue_t serialQueue = dispatch_queue_create("BIn.text", DISPATCH_QUEUE_SERIAL);
    
    for (id obj in array) {
        dispatch_async(serialQueue, ^{
            for (int i = 1; i <= 100; i++) {
                NSLog(@"%@ = %d",obj,i);
            }
        });
    }


一个全局并发队列,默认的优先级

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    for (id obj in array) {
        dispatch_async(globalQueue, ^{
            NSLog(@"%@ = %@",[NSThread isMainThread] ? @"主线程" : @"异步线程" ,[NSThread currentThread]);
            for (int i = 1; i <= 100; i++) {
                NSLog(@"%@ = %d",obj,i);
            }
        });
    }


我们还可以这样玩

dispatch_queue_t  bgQueue = dispatch_queue_create("text.create", NULL);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1做一些事情 ...");
        
        dispatch_async(dispatch_get_main_queue(), ^{ 
            
            NSLog(@"回到主线程中做一些事情,比如更新UI");
            
        }); // async无需等待block操作就可执行下面代码
        NSLog(@"2做一些事情 ...");
        
        dispatch_sync(dispatch_get_main_queue(), ^{ // sync要等待
            
            NSLog(@"回到主线程做一些事情");
            
            dispatch_async(bgQueue, ^{  // async不用等block执行完
                NSLog(@"创建一个新的队列,来做更多的事情 什么队列取决你的需求");
            });
        });  // sync操作完成之后才能继续执行下面的代码
        NSLog(@"3做一些事情 ...");
    });


如果想在dispatch_queue中所有的任务执行完成后在做某种操作,在串行队列中,可以把该操作放到最后一个任务执行完成后继续,但是在并行队列中怎么做呢。这就有dispatch_group 成组操作。比如

array = @[@"guo",@"bin",@"ai",@"chun",@"yan"];
    dispatch_queue_t queue = dispatch_queue_create("BIn.create", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    for (id obj in array) {
        dispatch_group_async(group, queue, ^{
            for (int i = 0; i < 100; i++) {
                NSLog(@"%@ = %d",obj,i);
            }
        });
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });


[b]dispatch_group_enter/dispatch_group_leave[/b]手动管理group关联的block的运行状态(或计数),进入和退出group次数必须匹配,下面的代码等同上面的

[b]注意:这两个[b]函数要配合使用,有enter要有leave,这样才能保证功能完整实现。也可以用这对函数来让一个闭包关联多个Groupdispatch_group_notify 用来监听任务组事件的执行完毕[/b][/b]

array = @[@"guo",@"bin",@"ai",@"chun",@"yan"];
    dispatch_queue_t queue = dispatch_queue_create("BIn.create", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    for (id obj in array) {
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            for (int i = 0; i < 100; i++) {
                NSLog(@"%@ = %d",obj,i);
            }
            dispatch_group_leave(group);
        });
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });


对于同步执行,GCD提供了一个简化方法叫做dispatch_apply。这个函数调用单一block多次

dispatch_apply可以利用多核的优势,所以输出的index顺序不是一定的 如果所在的队列是串行的,那么都是在主线程执行,而并行的话 代码是在主线程还是在异步这是不可控的.

dispatch_queue_t queue = dispatch_queue_create("dispatch_apply", NULL);
    //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(array.count, queue, ^(size_t index) {
        NSLog(@"index = %zu",index);
        NSLog(@"%@ = %@",[NSThread isMainThread] ? @"主线程" : @"异步线程",[NSThread currentThread]);
    });
    //dispatch_apply 和 dispatch_apply_f 是同步函数,会阻塞当前线程直到所有循环迭代执行完成。当提交到并发queue时,循环迭代的执行顺序是不确定的
    
    // dispatch_apply函数可是没有指定异步版本的。但是我们使用的可是一个为异步而生的API啊!所以我们只要用dispatch_async函数将所有代码推到后台就行了:
    dispatch_async(queue, ^{
        dispatch_apply(array.count, queue, ^(size_t index) {
            NSLog(@"index = %zu",index);
            NSLog(@"%@ = %@",[NSThread isMainThread] ? @"主线程" : @"异步线程",[NSThread currentThread]);
        });
    });


dispatch_barrier_async() 这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1");
    });
    for (id obj in array) {
        dispatch_async(concurrentQueue, ^(){
            for (int i = 0; i < 100; i++) {
                NSLog(@"dispatch-2 = %@",obj);
            }
        });
        
    }
    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4");
    });


Dispatch Sources

现代系统通常提供异步接口,允许应用向系统提交请求,然后在系统处理请求时应用可以继续处理自己的事情。Grand Central Dispatch正是基于这个基本行为而设计,允许你提交请求,并通过block和dispatch queue报告结果。

dispatch source是基础数据类型,协调特定底层系统事件的处理。Grand Central Dispatch支持以下dispatch source:

Timer dispatch source:定期产生通知

Signal dispatch source:UNIX信号到达时产生通知

Descriptor dispatch source:各种文件和socket操作的通知

数据可读

数据可写

文件在文件系统中被删除、移动、重命名

文件元数据信息改变

Process dispatch source:进程相关的事件通知

当进程退出时

当进程发起fork或exec等调用

信号被递送到进程

Mach port dispatch source:Mach相关事件的通知

Custom dispatch source:你自己定义并自己触发

Dispatch source替代了异步回调函数,来处理系统相关的事件。当你配置一个dispatch source时,你指定要监测的事件、dispatch queue、以及处理事件的代码(block或函数)。当事件发生时,dispatch source会提交你的block或函数到指定的queue去执行

下面是事件处理器能够获得的事件信息:
函数描述
dispatch_source_get_handle这个函数返回dispatch source管理的底层系统数据类型。

对于描述符dispatch source,函数返回一个int,表示关联的描述符

对于信号dispatch source,函数返回一个int,表示最新事件的信号数值

对于进程dispatch source,函数返回一个pid_t数据结构,表示被监控的进程

对于Mach port dispatch source,函数返回一个 mach_port_t 数据结构

对于其它dispatch source,函数返回的值未定义
dispatch_source_get_data这个函数返回事件关联的所有未决数据。

对于从文件中读取数据的描述符dispatch source,这个函数返回可以读取的字节数

对于向文件中写入数据的描述符dispatch source,如果可以写入,则返回正数值

对于监控文件系统活动的描述符dispatch source,函数返回一个常量,表示发生的事件类型,参考 dispatch_source_vnode_flags_t 枚举类型

对于进程dispatch source,函数返回一个常量,表示发生的事件类型,参考 dispatch_source_proc_flags_t 枚举类型

对于Mach port dispatch source,函数返回一个常量,表示发生的事件类型,参考 dispatch_source_machport_flags_t 枚举类型

对于自定义dispatch source,函数返回从现有数据创建的新数据,以及传递给 dispatch_source_merge_data 函数的新数据。
dispatch_source_get_mask这个函数返回用来创建dispatch source的事件标志

对于进程dispatch source,函数返回dispatch source接收到的事件掩码,参考 dispatch_source_proc_flags_t 枚举类型

对于发送权利的Mach port dispatch source,函数返回期望事件的掩码,参考 dispatch_source_mach_send_flags_t 枚举类型

对于自定义 “或” 的dispatch source,函数返回用来合并数据值的掩码。
看代码,

progressTotal = 0;
    myProgressView.progress = 0;
    _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0));
    dispatch_source_set_event_handler(_source, ^{
        size_t estimated = dispatch_source_get_data(_source);
        //        NSLog(@"zu = %zu",estimated);
        
        NSLog(@"%@ = %@",[NSThread currentThread],[NSThread isMainThread] ? @"主线程" : @"异步线程");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI");
            
            progressTotal += (float)estimated / 100;
            if (progressTotal >= 1) {
                [myProgressView setProgress:1 animated:YES];
                //                dispatch_suspend(_timerSource);   // 挂起 -> 还在
                dispatch_source_cancel(_timerSource); // 取消这个事件 -> 销毁它之前必须要恢复
                
                return;
            }
            [myProgressView setProgress:progressTotal animated:YES];
            
        });
        
    });
    dispatch_resume(_source);
    
    //  一个定时器
    /*计时器事件稍有不同。它们不使用handle/mask参数,计时器事件使用另外一个函数 dispatch_source_set_timer 来配置计时器。这个函数使用三个参数来控制计时器触发:
     start参数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和  dispatch_walltime 函数来创建它们。另外,常量  DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
     interval参数没什么好解释的。
     leeway参数比较有意思。这个参数告诉系统我们需要计时器触发的精准程度。所有的计时器都不会保证100%精准,这个参数用来告诉系统你希望系统保证精准的努力程度。如果你希望一个计时器没五秒触发一次,并且越准越好,那么你传递0为参数。另外,如果是一个周期性任务,比如检查email,那么你会希望每十分钟检查一次,但是不用那么精准。所以你可以传入60,告诉系统60秒的误差是可接受的。
     这样有什么意义呢?简单来说,就是降低资源消耗。如果系统可以让cpu休息足够长的时间,并在每次醒来的时候执行一个任务集合,而不是不断的醒来睡去以执行任务,那么系统会更高效。如果传入一个比较大的leeway给你的计时器,意味着你允许系统拖延你的计时器来将计时器任务与其他任务联合起来一起执行。*/
    dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
    _timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    double interval = 1 * NSEC_PER_SEC;
    dispatch_source_set_timer(_timerSource, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        NSLog(@"网络请求");

        dispatch_source_set_event_handler(_timerSource, ^{
            dispatch_sync(dispatch_get_main_queue(), ^{
                dispatch_source_merge_data(_source, getScopeInsideRandomValue(1, 30));
            });
        });
        dispatch_resume(_timerSource);
    });
// 获取一个随机整数,范围在[from,to),包括from,不包括to
int getScopeInsideRandomValue(int from, int to) {
    return (int)(from + (arc4random() % (to - from + 1)));
}


dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue)

dispatch_set_target_queue的两个作用:

用来给新建的queue设置优先级

dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);  
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);  
       
    dispatch_set_target_queue(serialQueue, globalQueue);  
    /* * 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。 
         */


第一个参数是自定义的queue(默认优先级就是global queue的default),而不是系统的queue(global/main)。因为你不能给系统的queue设置权限。通过上面设置,serialQueue 就有了与globalQueue一样的优先级。其实这个函数不仅可以设置queue的优先级,还可以设置queue之间的层级结构。

修改用户队列的目标队列,使多个serial queue在目标queue上一次只有一个执行:

它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说下面两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:

dispatch_set_target_queue(dispatchA, dispatchB);

那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:

dispatch_suspend(dispatchA);

则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。

dispatch_queue_t serialQueue = dispatch_queue_create("Guo.create",NULL);
    dispatchB = dispatch_queue_create("BIn.create",NULL);
    
    dispatchA = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, serialQueue);
    double interval = 1 * NSEC_PER_SEC;
    dispatch_source_set_timer(dispatchA, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
    
    dispatch_set_target_queue(dispatchA, dispatchB);
    
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        dispatch_source_set_event_handler(dispatchA, ^{
            NSLog(@"dispatchA");
        });
        dispatch_resume(dispatchA);
    });
    
    
    dispatch_async(dispatchB, ^{
        for (int i = 0; i < 10000; i++) {
            NSLog(@"B");
        }
    });


一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。

dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
        
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
        
   dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    dispatch_set_target_queue(queue1, targetQueue);
        
    dispatch_async(queue1, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"1");
        }
    });
        
    dispatch_async(queue2, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"2");
        }
    });
        
    dispatch_async(queue3, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"3");
        }
    });


dispatch_after( dispatch_time_t when, dispatch_queue_t
queue, dispatch_block_t
block); //延迟执行block

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after");
    });


本文我是在网上根据众多大咖的博客总结出来,花费了不少心思,才标为原创,下面是采集资源的出处:

http://justsee.iteye.com/blog/2233252



http://www.tuicool.com/articles/ZNn6Jz

http://blog.csdn.net/nogodoss/article/details/31346207



http://www.dreamingwish.com/article/gcdgrand-central-dispatch-jiao-cheng.html

http://www.th7.cn/Program/IOS/201411/322852.shtml[b][b]dispatch_group_enter/dispatch_group_leave[/b]

[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: