iOS多线程开发之GCD(中篇)
前文回顾:
上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue;第二种是直接使用系统提供的标准Dispatch Queue :Main Dispatch Queue和Global Dispatch Queue,具体的实现方式请参照上篇博客《iOS多线程开发之GCD(上篇)》。
这篇博客主要讲解以下苹果提供的一些常用GCD和代码示例及其注意点。
- dispatch_set_target_queue
- dispatch_after
- dispatch_once / dispatch_apply
- Dispatch Group
- dispatch_barrier_sync
- dispatch_suspend / dispatch_resume
- Dispatch Semaphore
一、dispatch_set_target_queue
dispatch_set_target_queue中涉及的代码示例来源于网络
1、dispatch_set_target_queue 更改Dispatch Queue的执行优先级
dispatch_queue_create函数生成的DisPatch Queue不管是Serial DisPatch Queue还是Concurrent Dispatch Queue,执行的优先级都与默认优先级的Global Dispatch queue相同,如果需要变更生成的Dispatch Queue的执行优先级则需要使用dispatch_set_target_queue函数。
dispatch_queue_t serialQueue = dispatch_queue_create("com.beckwang.www",NULL); dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,); // 第一个参数为要变更优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
dispatch_set_target_queue(serialQueue, globalQueue);
2、dispatch_set_target_queue作为执行阶层,修改队列的目标队列使多个serial queue在目标queue上一次只有一个执行
- (void)testTargetQueue2 {
//创建一个串行队列queue1
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
//创建一个串行队列queue2
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL); //使用dispatch_set_target_queue()实现队列的动态调度管理
dispatch_set_target_queue(queue1, queue2); /*
那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:
dispatch_suspend(dispatchA);
这时则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。
dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾
*/ dispatch_async(queue1, ^{
for (NSInteger i = ; i < ; i++) {
NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
[NSThread sleepForTimeInterval:0.5];
if (i == ) {
dispatch_suspend(queue2);
}
}
}); dispatch_async(queue1, ^{
for (NSInteger i = ; i < ; i++) {
NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
} }); dispatch_async(queue2, ^{
for (NSInteger i = ; i < ; i++) {
NSLog(@"queue2:%@, %ld", [NSThread currentThread], i);
}
});
}
适用场景:一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时候dispatch_set_target_queue将起到作用。
(1)没有使用dispatch_set_target_queue时:
- (void)testTargetQueue3 {
//1.创建目标队列
//dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL); //2.创建3个串行队列
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); //3.将3个串行队列分别添加到目标队列
//dispatch_set_target_queue(queue1, targetQueue);
//dispatch_set_target_queue(queue2, targetQueue);
//dispatch_set_target_queue(queue3, targetQueue); dispatch_async(queue1, ^{
NSLog(@"1 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"1 out");
}); dispatch_async(queue2, ^{
NSLog(@"2 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"2 out");
});
dispatch_async(queue3, ^{
NSLog(@"3 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"3 out");
});
}
打印结果:
-- ::51.915 Test[:] in
-- ::51.915 Test[:] in
-- ::51.915 Test[:] in
-- ::52.916 Test[:] out
-- ::53.921 Test[:] out
-- ::54.919 Test[:] out
结论:多个串行queue之间是并行的!
(2)使用dispatch_set_target_queue设置target
- (void)testTargetQueue3 {
//1.创建目标队列
dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL); //2.创建3个串行队列
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); //3.将3个串行队列分别添加到目标队列
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue); dispatch_async(queue1, ^{
NSLog(@"1 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"1 out");
}); dispatch_async(queue2, ^{
NSLog(@"2 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"2 out");
});
dispatch_async(queue3, ^{
NSLog(@"3 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"3 out");
});
}
打印结果:
-- ::33.667 Test[:] in
-- ::36.672 Test[:] out
-- ::36.672 Test[:] in
-- ::38.678 Test[:] out
-- ::38.679 Test[:] in
-- ::39.683 Test[:] out
结论:多个串行queue之间是串行的!
二、dispatch_after
如果需要延时处理某件事情,我们可以使用dispatch_after,需要注意的是dispatch_after并不是将任务追加到队列dispatch_queue后再根据时间参数延迟执行block代码,而是在指定时间后追加任务到到dispatch_queue。代码示例:
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, *NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"这里是dispatch_after测试");
}); // dispatch_get_main_queue ---> diapatch_get_gloab_queue 就可以更改执行线程
实现延时处理除了上面的GCD(dispatch_after)外,还可以通过以下方法:
(1)performSelector(NSObject)方法:
// 不带参数
[self performSelector:@selector(doSomething) withObject:self afterDelay:3.0f]; // 带参数
[self performSelector:@selector(delayDo:) withObject:@"paramtest" afterDelay:3.0f]; // 取消全部
[NSObject cancelPreviousPerformRequestsWithTarget:self]; // 取消不传参的方法
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayDo:) object:nil]; // 取消传参的方法
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayDo:) object:@"paramtest"];
(2)NSTimer的类方法:
// 不带参数
[NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(doSomething) userInfo:nil repeats:NO]; // 带参数
[NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(doSomething) userInfo:@"paramtest" repeats:NO];
使用NSTimer时注意事项可以参考我的另外一篇博客《实现定时器NSTimer的高逼格方法》
(3)sleep(NSThreed)
[NSThread sleepForTimeInterval:1.0f];
// 这里执行延迟处理代码
[self doSomething];
三、dispatch_once 和 dispacth_apply
dispatch_once整个app运行周期内只执行一次代码,多用于单例模式。
dispatch_once_t *predicate:一个全局的变量 dispatch_block_t block:block函数块
dispatch_apply让指定代码按设定次数多次执行,dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,如果队列是串行队列,则block任务只会同步执行,但是dispatch_apply是一个同步调用,block任务执行n次后才返回。
size_t iterations:执行次数 dispatch_queue_t queue:队列 void (^block)(size_t):block函数块
代码示例:
(1)dispatch_once
自定义block函数块
//定义block
typedef void (^BLOCK)(void); //将执行代码封装到block中
BLOCK myBlock = ^(){
static int count = ;
NSLog(@"count=%d",count++);
};
执行
// 只会执行一次
static dispatch_once_t predicate;
dispatch_once(&predicate, myBlock);
打印结果:count = 0;
(2) dispatch_apply
自定义block
//定义block
typedef void (^BLOCK)(size_t); //将函数封装到block
BLOCK myBlock = ^(size_t size){
static int count = ;
NSLog(@"count=%d",count++);
};
执行
dispatch_apply(, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), myBlock);
打印结果:
count =
count =
count =
count =
count =
显而易见,如果dispatch_apply的队列是自定义的串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
输出结果将是:
count =
count =
count =
count =
count =
dispatch_apply 可以处理一个任务重复执行次数量级比较大的应用场景,假设从服务器上获取一组数组数据(超过100个元素对象)然后进行字典转化模型
多线程并发处理:
// 多线程并发处理,可能造成线程爆炸及死锁
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); for (int i = ; i < ; i++){
dispatch_async(queue, ^{
// 字典转模型
});
}
dispatch_barrier_sync(dispatch_get_main_queue(), ^{
NSLog(@"主线程更新");
}); ---------------------------这里是分割线--------------------------- // dispatch_apply 方式 (优先选择)
NSArray *dictArray = nil; dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ dispatch_apply(dictArray.count, queue, ^(size_t index){
//字典转模型 }); dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主线程更新");
});
});
四、Dispatch Group
在追加到Dispatch Queue中的多个任务处理全部完毕之后想执行结束处理。如果只是使用一个Serial Dispatch Queue(串行队列)时,只要将想执行的处理全部追加到该串行队列中并在最后追加结束处理即可,但是在使用Concurrent Queue 时,可能会同时使用多个Dispatch Queue时,这就需要使用Dispatch Group。
- (void)testDispatchGroup{ dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.gcdgroup.www", DISPATCH_QUEUE_CONCURRENT); dispatch_group_async(group, queue, ^{
for (int i = ; i < ; i++) {
if (i == ) {
NSLog(@"test001");
}
}
}); dispatch_group_async(group, queue, ^{
NSLog(@"test002");
}); dispatch_group_async(group, queue, ^{
NSLog(@"test003");
}); dispatch_group_notify(group, queue, ^{
NSLog(@"全部完成");
});
}
打印结果:
-- ::57.449 Test[:] test002
-- ::57.449 Test[:] test003
-- ::57.449 Test[:] test001
-- ::57.449 Test[:] 全部完成
Dispatch Group广泛运用到异步获取网络数据最后汇总的情况,如异步获取多张网络图片资源后拼接成一张图片等等。
五、dispatch_barrier_async
在访问数据库和文件时,如前所述,使用Serial Dispatch Queue可避免数据资源的竞争问题。众所周知,写处理与写处理,写处理与读处理会发生数据一致性或数据竞争问题,但是读处理与读处理之前不存在数据一致性问题,为了提高效率我们可以这样设想:读处理可以追加到Concurrent Dispatch Queue(并发队列)中,而写处理在任意一个没有读取处理执行的状态下追加到Serial Dispatch Queue(串行队列)中(在写处理结束之前,读处理不可执行)。
代码示例:
- (void)testDispatchBarrier{ dispatch_queue_t queue = dispatch_queue_create("com.gcdbarrier.www", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"block001_read");
}); dispatch_async(queue, ^{ NSLog(@"block002_read");
}); dispatch_async(queue, ^{ NSLog(@"block003_read"); }); dispatch_barrier_sync(queue, ^{ NSLog(@"block004_write");
}); dispatch_async(queue, ^{ NSLog(@"block005_read");
}); dispatch_async(queue, ^{ NSLog(@"block006_read");
}); dispatch_async(queue, ^{ NSLog(@"block007_read");
}); }
打印结果:
-- ::27.936 Test[:] block003_read
-- ::27.936 Test[:] block002_read
-- ::27.936 Test[:] block001_read
-- ::27.937 Test[:] block004_write
-- ::27.937 Test[:] block005_read
-- ::27.937 Test[:] block007_read
-- ::27.937 Test[:] block006_read
六、dispatch_suspend / dispatch_resume
dispatch_suspend,dispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
// 挂起指定队列
dispatch_suspend(queue);
// 恢复指定队列
dispatch_resume(queue);
代码示例:
- (void)gcdSuspendResume{ dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
// 提交第一个block,延时5秒打印。
dispatch_async(queue, ^{
sleep();
NSLog(@"After 5 seconds...");
});
// 提交第二个block,也是延时5秒打印
dispatch_async(queue, ^{
sleep();
NSLog(@"After 5 seconds again...");
});
// 延时一秒
NSLog(@"sleep 1 second...");
sleep();
// 挂起队列
NSLog(@"suspend...");
dispatch_suspend(queue);
// 延时10秒
NSLog(@"sleep 10 second...");
sleep();
// 恢复队列
NSLog(@"resume...");
dispatch_resume(queue);
}
打印结果:
-- ::44.329 beck.wang[:] sleep second...
-- ::45.330 beck.wang[:] suspend...
-- ::45.330 beck.wang[:] sleep second...
-- ::49.333 beck.wang[:] After seconds...
-- ::55.331 beck.wang[:] resume...
-- ::00.336 beck.wang[:] After seconds again...
七、Dispatch Semaphore
dispatch_semaphore(信号量)是基于计数器的一种多线程同步机制,是GCD控制并发的一种方式。
信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。
在GCD中有三个函数是semaphore的操作,分别是:dispatch_semaphore_create、dispatch_semaphore_signal、dispatch_semaphore_wait。
1、dispatch_semaphore_create 创建具有初始值的信号量
// 输出一个dispatch_semaphore_t类型且值为value的信号量。这里的传入的参数value必须>=0,否则dispatch_semaphore_create会返回NULL。
dispatch_semaphore_t
dispatch_semaphore_create(long value); // 示例
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
2、dispatch_semaphore_signal 发送信号量,让信号量总数+1
long dispatch_semaphore_signal(dispatch_semaphore_tdsema)
3、dispatch_semaphore_wait 等待信号量,当信号总量< 0 的时候等待设置的timeout参数,否则就可以正常的执行,并让信号总量 -1
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
注意:timeout是dispatch_time_t类型,不可直接使用其他类型(int或者float等)。如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且dispatch_semaphore_wait所处线程获得了信号量,那么就继续向下执行并将信号量减1;如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
苹果给了两个timeout的宏定义,也是比较常用的。
DISPATCH_TIME_NOW //当前时间
DISPATCH_TIME_FOREVER // 一直等待
如果需要自定义timeout可以使用
dispatch_time_t
dispatch_time(dispatch_time_t when, int64_t delta); dispatch_time_t
dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
喜欢我的文章请点击关注哦,我将在以后的工作中争取写出更高质量的博客,交流分享!……^_^
iOS多线程开发之GCD(中篇)的更多相关文章
- iOS多线程开发之GCD(死锁篇)
上篇和中篇讲解了什么是GCD,如何使用GCD,这篇文章将讲解使用GCD中将遇到的死锁问题.有兴趣的朋友可以回顾<iOS多线程开发之GCD(上篇)>和<iOS多线程开发之GCD(中篇) ...
- iOS多线程开发之GCD(中级篇)
前文回顾: 上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue:第二 ...
- iOS多线程开发之GCD(基础篇)
总纲: GCD基本概念 GCD如何实现 GCD如何使用 队列和任务组合 一.GCD基本概念 GCD 全称Grand Central Dispatch(大中枢队列调度),是一套低层API,提供了⼀种新的 ...
- iOS多线程开发之NSOperation - 快上车,没时间解释了!
一.什么是NSOperation? NSOperation是苹果提供的一套多线程解决方案.实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象.代码可读性更高.可控性更强 ...
- iOS多线程开发之NSOperation
一.什么是NSOperation? NSOperation是苹果提供的一套多线程解决方案.实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象.代码可读性更高.可控性更强 ...
- iOS 多线程开发之OperationQueue(二)NSOperation VS GCD
原创Blog.转载请注明出处 blog.csdn.net/hello_hwc 欢迎关注我的iOS SDK具体解释专栏 http://blog.csdn.net/column/details/huang ...
- iOS多线程开发之NSThread
一.NSThread基本概念 NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同 ...
- 多线程开发之GCD
简介GCD本身是苹果公司为多核的并行运算提出的解决方案.GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器.GCD是Grand Central Dispatch的简称,它是基于C语言的. ...
- iOS 开发之 GCD 不同场景使用
header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...
随机推荐
- 022 component(组件)关联映射
Component关联映射: 目前有两个类如下: 值对象没有标识,而实体对象具有标识,值对象属于某一个实体,使用它重复使用率提升,而且更清析. 以上关系的映射称为component(组件)关联映射 在 ...
- 开涛spring3(7.4) - 对JDBC的支持 之 7.4 Spring提供的其它帮助
7.4 Spring提供的其它帮助 7.4.1 SimpleJdbc方式 Spring JDBC抽象框架提供SimpleJdbcInsert和SimpleJdbcCall类,这两个类通过利用JDB ...
- Intel CPU命名规则的简略解析
Intel的CPU命名规则一直不是特别清楚,而网上的很多解读是不准确,甚至是错误的,应该以官方文档为准.所以我在查阅官方资料的基础上,以一种简明扼要的方式记录下来.值得说明的是,这个解析只是简略的,一 ...
- Identity Service - 解析微软微服务架构eShopOnContainers(二)
接上一篇,众所周知一个网站的用户登录是非常重要,一站式的登录(SSO)也成了大家讨论的热点.微软在这个Demo中,把登录单独拉了出来,形成了一个Service,用户的注册.登录.找回密码等都在其中进行 ...
- python 收录集中实现线程池的方法
概念: 什么是线程池? 诸如web服务器.数据库服务器.文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务.构建服务器应用程序的一个过于简单的模型是:每当一个请求到达就创 ...
- java基础之数组常用操作
常用的对数组进行的操作 1.求数组中最大值,最小值 思路:假设下标为0的元素是最大值,遍历数组,依次跟max进行比较,如果有元素比这个max还大,则把这个值赋给max.最小值同样 public cla ...
- 小K的H5之旅-HTML5与CSS3部分新属性浅见
一.HTML部分 1.HTML5新特点 向下兼容.用户至上.化繁为简.无插件范式.访问通用性.引入语义.引入原生媒体支持.引入可编程内容 2.HTML5标签语法 可以省略的元素:空元素语法的元素{br ...
- 抓包工具-Wireshark(详细介绍与TCP三次握手数据分析)
功能使用的详细介绍 wireshark(官方下载网站: http://www.wireshark.org/),是用来获取网络数据封包,可以截取各种网络封包,显示网络封包的详细信息,包括http,TCP ...
- javacpp-opencv图像处理3:使用opencv原生方法遍历摄像头设备及调用(增加实时帧率计算方法)
javaCV图像处理系列: javaCV图像处理之1:实时视频添加文字水印并截取视频图像保存成图片,实现文字水印的字体.位置.大小.粗度.翻转.平滑等操作 javaCV图像处理之2:实时视频添加图片水 ...
- 玩转nodeJS系列:使用cluster创建nodejs单机多核集群(多进程)
前言: nodejs提供了cluster集群(支持端口共享的多进程),cluster基于child_process,process二次封装,方便我们使用该功能实现单机nodejs的web集群. 1.c ...