在App开发中经常会遇到多个线程同时向服务器取数据, 如果每个线程取得数据后都去刷新UI会造成界面的闪烁

也有可能出现部分数据还没有获取完毕造成程序crash

之前在网上看到很多是利用dispatch_group_asyncdispatch_group_tdispatch_group_notify 组合来实现的

比如这样:

将几个线程加入到group中, 然后利用group_notify来执行最后要做的动作

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个group
dispatch_group_t group = dispatch_group_create(); //创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //创建一个GCD线程1
dispatch_group_async(group, queue, ^{ NSLog(@"线程1");
}); //创建一个GCD线程2
dispatch_group_async(group, queue, ^{ NSLog(@"线程2");
}); //创建一个GCD线程3
dispatch_group_async(group, queue, ^{ NSLog(@"线程3");
}); //创建一个group通知
dispatch_group_notify(group, queue, ^{ NSLog(@"结束");
});
}

运行结果:

-- ::22.454 GCDDemo[:] 线程2
-- ::22.454 GCDDemo[:] 线程3
-- ::22.454 GCDDemo[:] 线程1
-- ::22.454 GCDDemo[:] 结束

看起来是3个线程无序运行, 最后等全部线程结束后才执行group结束动作. 看样子都很正常

但如果3个线程为异步操作呢, 比如网络请求

我们用异步计数试试看

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个group
dispatch_group_t group = dispatch_group_create(); //创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //创建一个GCD线程1
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程1");
});
}); //创建一个GCD线程2
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程2");
});
}); //创建一个GCD线程3
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程3");
});
}); //创建一个group通知
dispatch_group_notify(group, queue, ^{ NSLog(@"结束");
});
}

运行结果为:

-- ::57.610 GCDDemo[:] 结束
-- ::57.621 GCDDemo[:] 线程1
-- ::57.621 GCDDemo[:] 线程2
-- ::57.622 GCDDemo[:] 线程3

看, 这样就出问题了 先运行了我们原本要等线程都完成后才执行的动作

那要如何解决这个问题呢?

正确的方法应该是以上三个函数再配合

dispatch_group_enter(group)dispatch_group_leave(group)两个函数一起来使用,这样才能实现我们想要的最终效果。

dispatch_group_enter(dispatch_group_t group)
参数group不能为空,在异步任务开始前调用。

它明确的表明了一个 block 被加入到了队列组group中,此时group中的任务的引用计数会加1(类似于OC的内存管理)

dispatch_group_enter(group)必须与dispatch_group_leave(group)配对使用,

它们可以在使用dispatch_group_async时帮助你合理的管理队列组中任务的引用计数的增加与减少。

dispatch_group_leave(dispatch_group_t group)
参数group不能为空,在异步任务成功返回后调用。

它明确的表明了队列组里的一个 block 已经执行完成,队列组中的任务的引用计数会减1,

它必须与dispatch_group_enter(group)配对使用,dispatch_group_leave(group)的调用次数不能多于dispatch_group_enter(group)的调用次数。

当队列组里的任务的引用计数等于0时,会调用dispatch_group_notify函数。

我们试试看, 注意红色字体代码

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个group
__block dispatch_group_t group = dispatch_group_create(); //创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //group任务计数加3
dispatch_group_enter(group);
dispatch_group_enter(group);
dispatch_group_enter(group); //创建一个GCD线程1
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程1");
//group任务计数减1
dispatch_group_leave(group);
});
}); //创建一个GCD线程2
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程2");
//group任务计数减1
dispatch_group_leave(group);
});
}); //创建一个GCD线程3
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程3");
//group任务计数减1
dispatch_group_leave(group);
});
}); //创建一个group通知, 任务计数为0时自动调用
dispatch_group_notify(group, queue, ^{ NSLog(@"结束");
});
}

运行结果:

-- ::59.988 GCDDemo[:] 线程1
-- ::59.991 GCDDemo[:] 线程2
-- ::59.991 GCDDemo[:] 线程3
-- ::59.993 GCDDemo[:] 结束

这样就符合我们的预期了

还没结束, 不  上面的方法是可以正确的实现多线程同步了, 现在我们再看下另外一种解决办法

利用GCD信号量dispatch_semaphore_t来实现,

我们先看下什么是信号量

首先了解下信号量的几个方法
.dispatch_semaphore_create(long value);
创建信号量,传入的value值要大于等于0,返回一个信号量
.dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
如果信号量的value值大于0,则会往下执行并将value的值减1,否则,阻碍当前线程并等待timeout后再往下执行。如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程会自动往下执行。
.dispatch_semaphore_signal(dispatch_semaphore_t dsema);
返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。

实现过程:

  • 创建一个任务组dispatch_group
 dispatch_group_t group = dispatch_group_create();
  • 将每个请求包装成一个任务异步提交到任务组里,每个任务在一开始创建一个信号量,value值为0,任务最后在网络请求完成前进行信号量的等待,如果网络请求完成,则调用 'dispatch_semaphore_signal(semaphore);'对信号值加1,则线程不再进行信号量的等待,继续往下执行。
  • 当所有请求都完成时,会在dispatch_group_notify里的回调进行相应的处理。

我们上代码看看:

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个group
__block dispatch_group_t group = dispatch_group_create(); //创建一个队列
__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //创建一个信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //创建一个GCD线程1
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程1");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}); //创建一个GCD线程2
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程2");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}); //创建一个GCD线程3
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程3");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}); //创建一个group通知, 任务计数为0时自动调用
dispatch_group_notify(group, queue, ^{ NSLog(@"结束");
});
}

这样也实现了同步实现异步线程, 可能大家会有一个疑问, 不同线程之前的信号量是否会相互干扰呢,

或者说如果其中一个线程要耗费相当大的时间, 其他线程是否也会被阻塞呢,

我们来试验下, 给线程3多增加几个迭代, 然后在wait前后加上一下打印

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个group
__block dispatch_group_t group = dispatch_group_create(); //创建一个队列
__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //创建一个信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(); //创建一个GCD线程1
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程1");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程1等待");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"线程1完成");
}); //创建一个GCD线程2
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程2");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程2等待");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"线程2完成"); }); //创建一个GCD线程3
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) {
for (int i = ; i < ; i ++) { }
}
NSLog(@"线程3");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程3等待");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"线程3完成");
}); //创建一个group通知, 任务计数为0时自动调用
dispatch_group_notify(group, queue, ^{ NSLog(@"结束");
});
}

运行结果为:

-- ::58.814 GCDDemo[:] 线程2等待
-- ::58.814 GCDDemo[:] 线程1等待
-- ::58.814 GCDDemo[:] 线程3等待
-- ::58.823 GCDDemo[:] 线程1
-- ::58.823 GCDDemo[:] 线程2
-- ::58.823 GCDDemo[:] 线程2完成
-- ::58.823 GCDDemo[:] 线程1完成
-- ::17.793 GCDDemo[:] 线程3
-- ::17.793 GCDDemo[:] 线程3完成
-- ::17.794 GCDDemo[:] 结束

好像看起来线程1, 2没有受到线程3的影响

我们再增加线程3的耗时看看,

//创建一个GCD线程3
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) {
for (int i = ; i < ; i ++) { }
}
NSLog(@"线程3");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程3等待");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"线程3完成");
});

我们多运行几次, 发现会出现这样的结果

-- ::37.975 GCDDemo[:] 线程3等待
-- ::37.975 GCDDemo[:] 线程2等待
-- ::37.975 GCDDemo[:] 线程1等待
-- ::37.984 GCDDemo[:] 线程1
-- ::37.984 GCDDemo[:] 线程2
-- ::37.984 GCDDemo[:] 线程3完成
-- ::37.985 GCDDemo[:] 线程2完成

线程3先打印了执行完, 所以看不同线程去侦测同一个信号量的时候是会有干扰的, 但是还是会等全部线程执行结束后才会去执行notify动作

那给每一个线程分别创建一个信号量呢?

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个group
__block dispatch_group_t group = dispatch_group_create(); //创建一个队列
__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //创建一个信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create();
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(); //创建一个GCD线程1
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程1");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程1等待");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"线程1完成");
}); //创建一个GCD线程2
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) { }
NSLog(@"线程2");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore2);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程2等待");
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"线程2完成"); }); //创建一个GCD线程3
dispatch_group_async(group, queue, ^{ //模拟异步耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) {
for (int i = ; i < ; i ++) { }
}
NSLog(@"线程3");
//完成迭代后, 增加信号量
dispatch_semaphore_signal(semaphore3);
}); //在迭代完成之前, 信号量等待
NSLog(@"线程3等待");
dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
NSLog(@"线程3完成");
}); //创建一个group通知, 任务计数为0时自动调用
dispatch_group_notify(group, queue, ^{ NSLog(@"结束");
});
}

多运行几次, 看起来每次都是只有线程3等待, 1, 2线程会自己正常完成

这样就OK了,  所以尽量每一个线程创建一个信号量, 避免相互干扰

关于GCD同步组实现多个异步线程的同步执行中的注意点的更多相关文章

  1. c#异步线程:同步调用,异步调用,异步回调

    定义一个异步线程类: public class AsyEventClass { private static ILog logger = LogManager.GetLogger(MethodBase ...

  2. 用GCD线程组与GCD信号量将异步线程转换为同步线程

    有时候我们会碰到这样子的一种情形: 同时获取两个网络请求的数据,但是网络请求是异步的,我们需要获取到两个网络请求的数据之后才能够进行下一步的操作,这个时候,就是线程组与信号量的用武之地了. #impo ...

  3. 【C#】C#线程_混合线程的同步构造

    目录结构: contents structure [+] 一个简单的混合锁 FCL中的混合锁 ManualResetEventSlim类和SemaphoreSlim类 Monitor类和同步块 Rea ...

  4. Java多线程之线程的同步

    Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...

  5. iOS边练边学--GCD的基本使用、GCD各种队列、GCD线程间通信、GCD常用函数、GCD迭代以及GCD队列组

    一.GCD的基本使用 <1>GCD简介 什么是GCD 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数   GCD的优势 G ...

  6. 深入理解非阻塞同步IO和非阻塞异步IO

    这两篇文章分析了Linux下的5种IO模型 http://blog.csdn.net/historyasamirror/article/details/5778378 http://blog.csdn ...

  7. 异步线程编程,线程池,线程组,后面涉及ThreadLocal在理解

    join模拟订单 package com.future.demo.future; /** * * * @author Administrator * */ public class NormalThr ...

  8. (原创)C++半同步半异步线程池2

    (原创)C++半同步半异步线程池 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 线程池可以高效的处理任务,线程池中开启多个线程,等待同步队列中的任务到来,任务到来多个线程 ...

  9. 异步IO比同步阻塞IO性能更好吗?为什么?

    最近在看node.js, 介绍中提到node是异步io的方式实现, 性能比同步阻塞io的更好. 对于一个request而言, 如果我们依赖io的结果, 异步io和同步阻塞io都是要等到io完成才能继续 ...

随机推荐

  1. WebDriver(Selenium2) 常见异常及处理方法

    http://uniquepig.iteye.com/blog/1568197 Exception NoSuchElementException Solutions    1. Check the l ...

  2. python 一遍式四则运算

    #!/usr/bin/python #coding: utf-8 INTEGER = 'INTEGER' PLUS = '+' MINUS = '-' MUL = '*' DIV = '/' LC = ...

  3. tempnam问题

    tempnam()函数创建一个具有唯一文件名的临时文件 若成功,则返回新的临时文件名,若失败,则返回false 失败原因 c:\windows\temp文件夹不具备读写权限(即 不是超级管理员)

  4. 优化EF性能

    本文介绍一些改善EF代码.优化其性能的相关方法,如NoTracking,GetObjectByKey, Include等,还包括编译查询.存储模型视图以及冲突处理等内容.. l         Mer ...

  5. Android 的Camera架构介绍

    http://java-admin.iteye.com/blog/452464   第一部分 Camera概述Android的Camera包含取景器(viewfinder)和拍摄照片的功能.目前And ...

  6. 程序ajax请求公共组件app-jquery-http.js中url参数部分的项目应用

    结合微信登录以及微信支付的案例:= =||| (案例比较奇葩复杂) 简述项目流程: 1.获取用于公众号支付的openid(公众平台):在微信内置浏览器中打开网页链接,刚进入页面就通过微信公众平台获取该 ...

  7. 结对编程--Goldpoint Game

    黄金点游戏 黄金点游戏描述: N个同学(N通常大于10),每人写一个0~100之间的有理数 (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0.618(所谓黄金分割常数),得到G值. ...

  8. (中等) POJ 1703 Find them, Catch them,带权并查集。

    Description The police office in Tadu City decides to say ends to the chaos, as launch actions to ro ...

  9. (简单) POJ 1511 Invitation Cards,SPFA。

    Description In the age of television, not many people attend theater performances. Antique Comedians ...

  10. Activiti----hellowWorld(使用H2数据库)

    1.项目结构 2.pom <dependencies> <dependency> <groupId>junit</groupId> <artifa ...