关于GCD同步组实现多个异步线程的同步执行中的注意点
在App开发中经常会遇到多个线程同时向服务器取数据, 如果每个线程取得数据后都去刷新UI会造成界面的闪烁
也有可能出现部分数据还没有获取完毕造成程序crash
之前在网上看到很多是利用dispatch_group_async
、dispatch_group_t
与dispatch_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同步组实现多个异步线程的同步执行中的注意点的更多相关文章
- c#异步线程:同步调用,异步调用,异步回调
定义一个异步线程类: public class AsyEventClass { private static ILog logger = LogManager.GetLogger(MethodBase ...
- 用GCD线程组与GCD信号量将异步线程转换为同步线程
有时候我们会碰到这样子的一种情形: 同时获取两个网络请求的数据,但是网络请求是异步的,我们需要获取到两个网络请求的数据之后才能够进行下一步的操作,这个时候,就是线程组与信号量的用武之地了. #impo ...
- 【C#】C#线程_混合线程的同步构造
目录结构: contents structure [+] 一个简单的混合锁 FCL中的混合锁 ManualResetEventSlim类和SemaphoreSlim类 Monitor类和同步块 Rea ...
- Java多线程之线程的同步
Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...
- iOS边练边学--GCD的基本使用、GCD各种队列、GCD线程间通信、GCD常用函数、GCD迭代以及GCD队列组
一.GCD的基本使用 <1>GCD简介 什么是GCD 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 GCD的优势 G ...
- 深入理解非阻塞同步IO和非阻塞异步IO
这两篇文章分析了Linux下的5种IO模型 http://blog.csdn.net/historyasamirror/article/details/5778378 http://blog.csdn ...
- 异步线程编程,线程池,线程组,后面涉及ThreadLocal在理解
join模拟订单 package com.future.demo.future; /** * * * @author Administrator * */ public class NormalThr ...
- (原创)C++半同步半异步线程池2
(原创)C++半同步半异步线程池 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 线程池可以高效的处理任务,线程池中开启多个线程,等待同步队列中的任务到来,任务到来多个线程 ...
- 异步IO比同步阻塞IO性能更好吗?为什么?
最近在看node.js, 介绍中提到node是异步io的方式实现, 性能比同步阻塞io的更好. 对于一个request而言, 如果我们依赖io的结果, 异步io和同步阻塞io都是要等到io完成才能继续 ...
随机推荐
- WEKA使用教程(经典教程转载)
http://blog.csdn.net/yangliuy/article/details/7589306 WEKA使用教程(经典教程转载) 标签: lift算法csv数据挖掘class任务 2012 ...
- log4j配置日志系统
1. lib里加入3个包 slf4j-api, slf4j-log4j12, log4j 2. 在src下 创建log4j.properties ### direct log messages to ...
- MC34063+MOSFET扩流 12V-5V 折腾出了高效率电路(转)
源:http://www.amobbs.com/thread-5484710-1-1.html 从网上找到一些MC34063扩流降压电路图,一个个的试,根本达不到我的基本要求,全都延续了34063的降 ...
- POJ 2482 Stars in Your Window
线段树+离散化+扫描线 AC之后,又认真读了一遍题目,好文章. #include<cstdio> #include<map> #include<algorithm> ...
- Unity3d之流光效果
所谓流光效果,如一个图片上一条刀光从左闪到右边,以下为实现代码: c#代码: using System; using UnityEngine; public class WalkLightEffect ...
- [iOS] 响应式编程开发-ReactiveCocoa(一)
什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...
- LED调光,PFM即pulse frequence modulation
PWM不是唯一的调制方式,可以PWM,也可以PFM,也可以混合调制. PWM即pulse width modulation的缩写,脉冲宽度调制,保持开关周期不变,调节开关导通时间. PFM即pulse ...
- input有许多,点击按钮使用form传递文本框的值
input有许多,点击按钮使用form传递文本框的值 <form name="form1" method="post" action="< ...
- UVa11555 - Aspen Avenue
今晚CF GYM A题,神坑.. 原题: Aspen Avenue ``Phew, that was the last one!'' exclaimed the garden helper Tim a ...
- IOS开发-ObjC-Category的使用
在IOS移动App开发中,经常会出现以下情况:定义好了一个类,但后来需求升级或改变之后需要对这个类增加功能,这样的话往往需要修改类的结构,这样就会导致不能预期的问题产生,所以Obj-C提供了一种叫做C ...