本篇就不废话啦,接着上篇记录我见过或者使用过的与GCD相关的API。由于一些API使用的非常少,用过之后难免会忘记,还是记录一下比较好。

6.dispatch_group_wait

该API依然是与dispatch_group配合使用。它会阻塞当前所在的线程,直到前面的blocks 执行完成,或者超时的时候返回。



大致意思是:该方法会同步的等待之前比较的block 对象们完成,如果在给定的时间内没有完成,该方法就会返回。如果在给定的时间超时前完成,则返回0,否则就返回一个非零的值。

示例代码:

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 5; i++) {
        dispatch_group_async(group, queue, ^{
            [NSThread sleepForTimeInterval:i];
            NSLog(@"并发%d结束----线程:%@", i,[NSThread currentThread]);
        });
    }
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
    long result = dispatch_group_wait(group, time);
    if (result) {
        NSLog(@"超时了");
    } else {
        NSLog(@"执行完毕");
    }
// 打印结果是:
2016-07-12 14:04:17.980 PractiseProject[4537:155453] 并发0结束----线程:<NSThread: 0x7fbbe8e115e0>{number = 3, name = (null)}
2016-07-12 14:04:18.981 PractiseProject[4537:155441] 并发1结束----线程:<NSThread: 0x7fbbe8f5ee60>{number = 4, name = (null)}
2016-07-12 14:04:19.980 PractiseProject[4537:155338] 超时了
2016-07-12 14:04:19.981 PractiseProject[4537:155456] 并发2结束----线程:<NSThread: 0x7fbbe8f5d260>{number = 5, name = (null)}
2016-07-12 14:04:20.985 PractiseProject[4537:155464] 并发3结束----线程:<NSThread: 0x7fbbe8f60e90>{number = 6, name = (null)}
2016-07-12 14:04:21.984 PractiseProject[4537:155453] 并发4结束----线程:<NSThread: 0x7fbbe8e115e0>{number = 3, name = (null)}

由于dispatch_group_wait会阻塞线程,在dispatch_group_wait后面的代码并不会执行,如果我们在主线程中执行上面的代码段,则会阻塞UI界面。所以我们应该在子线程中执行上面的代码片段(用一个dispatch_async包起来)。

如果我们设置wait的时间为永远的话,由于在子线程中执行的任务总有结束的时候,那么dispatch_group_wait之后执行的代码就效果就与上一篇中的dispatch_group_notify 的功能类似啦。

完整的示例代码:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        dispatch_group_t group = dispatch_group_create();
        for (int i = 0; i < 5; i++) {
            dispatch_group_async(group, queue, ^{
                [NSThread sleepForTimeInterval:i];
                NSLog(@"并发%d结束----线程:%@", i,[NSThread currentThread]);
            });
        }
        
        long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        if (result) {
            NSLog(@"超时了");
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"执行完毕,回主线程更新UI");
            });
        }
    });

7.dispatch_apply

在上面的代码中,我们使用for循环来创建5个并发执行的任务。但是for循环调度还是会有那么一点点的性能开销,而使用dispatch_apply可以避免这一点性能开销(我们都知道迭代器是使用for循环效率要高一点)。

大致意思是:该方法会等apply 中多次迭代调用的block全都执行完成后,才会返回,所以dispatch_apply会阻塞当前线程,我们得避免在主线程中使用dispatch_apply。另外,说明中已经说的很清楚了,如果我们使用dispatch_get_global_queue创建的串行队列,那么传入的block任务是并发执行的。如果我们在串行队列中执行该方法,会发生死锁,所以第二个参数,千万不要传串行队列。如果我们使用dispatch_queue_create创建的并发队列,block任务依然是顺序执行的。

下面看一下示例代码以及运行结果:

* dispatch_apply / dispatch_queue_create /并发队列:*

    dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        dispatch_apply(5, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:index];
            NSLog(@"并发%zu---%@",index,[NSThread currentThread]);
        });
        NSLog(@"done - %@",[NSThread currentThread]);
    });
    NSLog(@"主线程");
// 输出结果:
2016-07-12 16:09:33.856 PractiseProject[5496:207665] 主线程
2016-07-12 16:09:33.857 PractiseProject[5496:207710] 并发0---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:34.860 PractiseProject[5496:207710] 并发1---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:36.864 PractiseProject[5496:207710] 并发2---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:39.867 PractiseProject[5496:207710] 并发3---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:43.872 PractiseProject[5496:207710] 并发4---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:43.873 PractiseProject[5496:207710] done - <NSThread: 0x7f950053a920>{number = 3, name = (null)}

* dispatch_apply / dispatch_get_global_queue:*

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        dispatch_apply(5, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:index];
            NSLog(@"并发%zu---%@",index,[NSThread currentThread]);
        });
        NSLog(@"done - %@",[NSThread currentThread]);
    });
    
    NSLog(@"主线程");
// 输出结果:
2016-07-12 16:15:26.634 PractiseProject[5544:210845] 主线程
2016-07-12 16:15:26.634 PractiseProject[5544:210882] 并发0---<NSThread: 0x7f8733816bc0>{number = 3, name = (null)}
2016-07-12 16:15:27.637 PractiseProject[5544:210887] 并发1---<NSThread: 0x7f8731517e10>{number = 2, name = (null)}
2016-07-12 16:15:28.637 PractiseProject[5544:210893] 并发2---<NSThread: 0x7f8731412d50>{number = 4, name = (null)}
2016-07-12 16:15:29.636 PractiseProject[5544:210899] 并发3---<NSThread: 0x7f8731448cc0>{number = 5, name = (null)}
2016-07-12 16:15:30.635 PractiseProject[5544:210882] 并发4---<NSThread: 0x7f8733816bc0>{number = 3, name = (null)}
2016-07-12 16:15:30.635 PractiseProject[5544:210893] done - <NSThread: 0x7f8731412d50>{number = 4, name = (null)}

8.dispatch_semaphore

在我们使用多线程处理多个并发任务,而这多个并发任务有资源竞争的时候,就需要一种机制,在资源不够用时,让新的任务处于等待状态,当有可用资源时,等待的任务在按序依次执行。

像这一类问题除了可以用NSOperation,设置最大并发数外,还可以使用信号量。

这里涉及到的API有如下几个:

dispatch_semaphore_t dispatch_semaphore_create(long value);

创建信号量的方法,如果初始值小于0,则会返回NULL,即信号量创建失败。

参数:value 表示初始的信号量个数,可以理解为资源个数,或者最大并发个数。

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

参数:第一个参数为信号量对象,第二个参数是等待超时的时间。

讲解:该方法相当于任务开始前的检查,需要注意的是该方法会阻塞当前线程。如果此时信号量的值大于0,会返回0,并且代码会继续往下执行;如果此时信号量的值等于0,那么此时该方法会阻塞当前线程,等待timeout 的时间。如果在超时的时间内,依然没有可用的资源,那么该方法会返回一个非0的值。

该方法执行时,会使信号量的值减1。

long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

该方法应该在任务执行完毕时调用,它会使信号量的值加0。

下面用一段实际代码演示GCD信号量的使用:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 在子线程中执行,防止阻塞主线程
    dispatch_async(queue, ^{
        // 创建一个有3个资源的信号量
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
        for (int i = 0; i < 6; i++) {
            // 检测还有多少个资源,执行后会使资源数减少1
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_async(queue, ^{
                NSLog(@"开始执行任务%d---%@",i,[NSThread currentThread]);
                [NSThread sleepForTimeInterval:6 - i];
                NSLog(@"完成任务%d---%@",i,[NSThread currentThread]);
                // 表示资源使用完毕,会使资源数加1
                dispatch_semaphore_signal(semaphore);
            });
        }
    });
// 输出结果:
2016-07-13 17:23:53.178 PractiseProject[4973:196435] 开始执行任务1---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:53.178 PractiseProject[4973:196436] 开始执行任务0---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:23:53.178 PractiseProject[4973:196437] 开始执行任务2---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:57.179 PractiseProject[4973:196437] 完成任务2---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:57.179 PractiseProject[4973:196437] 开始执行任务3---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:58.182 PractiseProject[4973:196435] 完成任务1---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:58.182 PractiseProject[4973:196435] 开始执行任务4---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:59.179 PractiseProject[4973:196436] 完成任务0---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:23:59.179 PractiseProject[4973:196436] 开始执行任务5---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196435] 完成任务4---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196437] 完成任务3---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196436] 完成任务5---<NSThread: 0x7fc070722030>{number = 3, name = (null)}

9.dispatch_source中的timer

dispatch_source_t 的类型有很多种:

#define DISPATCH_SOURCE_TYPE_DATA_ADD 
#define DISPATCH_SOURCE_TYPE_DATA_OR 
#define DISPATCH_SOURCE_TYPE_MACH_RECV 
#define DISPATCH_SOURCE_TYPE_MACH_SEND 
#define DISPATCH_SOURCE_TYPE_PROC 
#define DISPATCH_SOURCE_TYPE_READ 
#define DISPATCH_SOURCE_TYPE_SIGNAL 
#define DISPATCH_SOURCE_TYPE_TIMER 
#define DISPATCH_SOURCE_TYPE_VNODE 
#define DISPATCH_SOURCE_TYPE_WRITE 
#define DISPATCH_SOURCE_TYPE_MEMORYPRESSURE

这里记录的是dispatch_source 中定时器timer的用法。

这里我就用dispatch_source 封装一个timer 的方法,可以在传入两个block,分别在循环执行,结束时执行。当然咯,下面这个方法还可以再加一个间隔时间参数。

- (void)startTimerWithTimeout:(NSTimeInterval)timeout eventBlock:(void (^)())eventBlock endBlock:(void (^)())endBlock
{
    __block NSTimeInterval tempTimeout = timeout;
    // 创建一个队列,不管你创建什么类型的队列,最终event_handler 都是在子线程中执行的
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 创建一个计时器类型的源
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 设置定时器参数,定时器开始的时间、每隔多久执行一次、精度(可以延迟的纳秒数,最高精度是0,实际还是会有偏差,感觉没什么卵用)
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"EVET ---- %@",[NSThread currentThread]);
        if (tempTimeout <= 0) {
            // 倒计时结束,取消源
            dispatch_source_cancel(timer);
            // 回到主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                // 倒计时结束时界面的UI的更新
                if (endBlock) {
                    endBlock();
                }
            });
        } else {
            // 回到主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                // 做界面的UI的更新
                if (eventBlock) {
                    eventBlock();
                }
            });
            
            tempTimeout--;
        }
    });
    dispatch_resume(timer);
}

封装好之后,调用起来就非常的Easy啦,并且看起来也挺舒服的。其实你可以把上面的方法封装成一个工具类方法。

    [self startTimerWithTimeout:30 eventBlock:^{
        NSLog(@"定时执行---%@",[NSThread currentThread]);
    } endBlock:^{
        NSLog(@"结束执行---%@",[NSThread currentThread]);
    }];

10.dispatch_source 中神奇的数据合并

上面介绍了dispatch_source 有多种类型,发现一种神奇的类型DISPATCH_SOURCE_TYPE_DATA_ADD,这种类型的source 有什么特别之处呢?

假如我们并发执行多个任务,这种类型的source 会在任务完成时,将data 加1,然后如果主线程比较空闲,那么event_handler就会多次调用,而如果主线程恰好比较忙碌,那么就会将任务合并,event_handler调用次数就会比较少。

还是先上一个代码范例:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
    dispatch_source_set_event_handler(source, ^{
        unsigned long completion = dispatch_source_get_data(source);
        NSLog(@"完成的任务个数:%lu----%@",completion,[NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI");
        });
    });
    
    dispatch_resume(source);
    dispatch_async(queue, ^{
        NSLog(@"网络线程---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        dispatch_source_merge_data(source, 1);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"网络线程---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        dispatch_source_merge_data(source, 1);
    });
//打印结果:
2016-07-13 15:38:45.911 PractiseProject[4130:150701] 网络线程---<NSThread: 0x7f9e12c10c90>{number = 2, name = (null)}
2016-07-13 15:38:45.911 PractiseProject[4130:150694] 网络线程---<NSThread: 0x7f9e12da6cb0>{number = 3, name = (null)}
2016-07-13 15:38:47.915 PractiseProject[4130:150701] 完成的任务个数:2----<NSThread: 0x7f9e12c10c90>{number = 2, name = (null)}
2016-07-13 15:38:47.915 PractiseProject[4130:150659] 更新UI
// 这也是打印结果:
2016-07-13 15:48:56.601 PractiseProject[4212:155405] 网络线程---<NSThread: 0x7fe390e09270>{number = 2, name = (null)}
2016-07-13 15:48:56.601 PractiseProject[4212:155411] 网络线程---<NSThread: 0x7fe390e0f800>{number = 3, name = (null)}
2016-07-13 15:48:59.304 PractiseProject[4212:155405] 完成的任务个数:1----<NSThread: 0x7fe390e09270>{number = 2, name = (null)}
2016-07-13 15:48:59.330 PractiseProject[4212:155377] 更新UI
2016-07-13 15:49:01.309 PractiseProject[4212:155415] 完成的任务个数:1----<NSThread: 0x7fe390e1d630>{number = 4, name = (null)}
2016-07-13 15:49:01.309 PractiseProject[4212:155377] 更新UI

它会根据主线程的繁忙与空闲,以及每个任务完成时的时间,减少返回次数或者每次返回。使用场景主要可以用在同时执行多个任务,任务的完成个数这种情况。

GCD API 记录 (三)的更多相关文章

  1. GCD API记录(二)

    前言 这是关于GCD的第二篇文章,GCD的API有100多个,通过快捷键Option + 单击,可以在Reference中的Grand Central Dispatch (GCD) Reference ...

  2. {django模型层(二)多表操作}一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询、分组查询、F查询和Q查询

    Django基础五之django模型层(二)多表操作 本节目录 一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询.分组查询.F查询和Q查询 六 xxx 七 ...

  3. Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群

    Redis总结(五)缓存雪崩和缓存穿透等问题   前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...

  4. TFS API:三、TFS WorkItem添加和修改、保存

    TFS API:三.TFS  WorkItem添加和修改.保存 WorkItemStore:表示跟踪与运行 Team Foundation Server的服务器的工作项客户端连接. A.添加工作项 1 ...

  5. [转]ASP.NET Web API(三):安全验证之使用摘要认证(digest authentication)

    本文转自:http://www.cnblogs.com/parry/p/ASPNET_MVC_Web_API_digest_authentication.html 在前一篇文章中,主要讨论了使用HTT ...

  6. ASP.NET Web API(三):安全验证之使用摘要认证(digest authentication)

    在前一篇文章中,主要讨论了使用HTTP基本认证的方法,因为HTTP基本认证的方式决定了它在安全性方面存在很大的问题,所以接下来看看另一种验证的方式:digest authentication,即摘要认 ...

  7. struts2的action访问servlet API的三种方法

    学IT技术,就是要学习... 今天无聊看看struts2,发现struts2的action访问servlet API的三种方法: 1.Struts2提供的ActionContext类 Object g ...

  8. HOOK API(三)—— HOOK 所有程序的 MessageBox

    HOOK API(三) —— HOOK 所有程序的 MessageBox 0x00 前言 本实例要实现HOOK MessageBox,包括MessageBoxA和MessageBoxW,其实现细节与H ...

  9. 【高德地图API】从零开始学高德JS API(三)覆盖物——标注|折线|多边形|信息窗口|聚合marker|麻点图|图片覆盖物

    原文:[高德地图API]从零开始学高德JS API(三)覆盖物——标注|折线|多边形|信息窗口|聚合marker|麻点图|图片覆盖物 摘要:覆盖物,是一张地图的灵魂.有覆盖物的地图,才是完整的地图.在 ...

随机推荐

  1. POJ1830开关问题

    这题答案就是2^自由元的数目,原因是自由元可以取1或者0,所以就是ans<<1 由于只要求自由元的数目,所以高斯消元可以直接消后面的,不做前面的了,对答案没有影响 #include< ...

  2. hdu 5131(2014 广州—模拟)

    题意:给你n个人以及他们的杀人数.先按杀人数从大到小排名输出,然后是一些询问 一个人名,①输出杀人数比他大的人数和+1:②如果有人杀人数和他一样而且名字的字典序比他小,输出人数+1,没有则无视. #i ...

  3. Windows下使用notepad++对文本进行行列转换

    行转列: Ctrl + F  选择替换 查找目标:填写指定的内容 替换为:\r\n 查找模式:正则表达式 单击替换或全部替换按钮 列转行: Ctrl + F  选择替换 查找目标:\r\n 替换为:不 ...

  4. 提高Mysql查询速度的一些建议(转).

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  5. python中常见错误及try-except 的用法

    1.常见的错误 我们在使用python过程中会出现: (1)SyntaxError 句法错误. (2)IndentationError 缩进错误. (3)NameError 变量未定义错误. (4)T ...

  6. git修改远程仓库地址

    方法有三种: 1.修改命令 git remote set-url origin [url] 例如: git remote set-url origin gitlab@gitlab.chumob.com ...

  7. Java HashMap的扩容

    最近博主参加面试,发现自己对于Java的HashMap的扩容过程理解不足,故最近在此进行总结. 首先说明博主德Java为1.8版本 HashMap中的变量 首先要了解HashMap的扩容过程,我们就得 ...

  8. font-spider利器对webfont网页字体压缩使用

    http://font-spider.org/ npm install font-spider -g hyheilizhitij(汉仪黑荔枝体简) //引入 @font-face{ font-fami ...

  9. 模仿天猫实战【SSM版】——项目起步

    前言:现在自己的学习似乎遇到了瓶颈,感觉学习了 SSM 之后有一些迷茫,不知道接下来该往哪里去努力了,我觉得这是个很不好的状态,为了度过这段时期,我准备把天猫模仿下来(给自己找点事做)之后开始去巩固 ...

  10. 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- CustomYieldInstruction 自定义中断指令

    ActionScript3脚本引擎为了方便热更新逻辑开发,提供的从脚本继承Unity类库功能在一些情况下可以提供开发的便利. 这次来建立一个示例,演示一下如何在脚本中自定义协程中断指令 Unity中的 ...