iOS实录:GCD使用小结(一)
导语:在iOS中,多线程方案有四种:pthread、NSThread、NSOperation & NSOperationQueue 和 GCD,但是开发中GCD使用得最多,本文主要总结一下我使用GCD的情况。
一、GCD(Grand Central Dispatch)概述
1、基本概念
GCD允许程序将任务切分为多个单一任务,提交至Dispatch Queue,然后系统调度线程,实现并发或者串行地执行任务。GCD隐藏了内部线程的调度,开发者只需要关注创建或获取队列,然后将Block追加到队列中即可。
在iOS中有两种队列,分别是串行队列和并发队列
串行队列:同一时间队列中只有一个任务在执行,每个任务只有在前一个任务执行完成后才能开始执行。主队列(通过dispatch_get_main_queue()获取,提交至主队列的任务会在主线程中执行) 就是串行队列,也可以使用dispatch_queue_create创建串行队列。

- 并发队列:这些任务会按照被添加的顺序开始执行。但是任意时刻有多个Block(任务)运行,这个完全是取决于GCD。并发队列可以使用dispatch_queue_create创建,也可以获取进程中的全局队列,全局队列有:高、中(默认)、低三个优先级队列。可以调用dispatch_get_global_queue函数传入相应优先级来访问队列。

同步执行:阻塞当前线程,直到当前block中任务执行完毕才返回。同步并不创建新线程。不能使用sync将任务添加到主队列,这样会造成死锁。
异步执行:不会阻塞当前线程,函数会立即返回, block会在后台异步执行;异步必定会开启新线程。
说明1:有些博客中将并发队列说成 并行队列,这是不对的。因为并行是多个事件在同一时刻发生,而并发是多个事件在同一时间间隔发生;并行完全依赖处理器的核数。而并发才能充分的利用处理器的每一个核,以达到最高的处理性能。
说明2:队列不等于线程。 作为开发者的我们,只是将Block添加进合适的GCD队列,真正的线程的调度是由系统完成的;无论同步(sync)还是异步(async)向主队列提交Block,最终Block都是在主线程中执行;同步(sync)往非主队列中提交Block,会在当前线程中执行; 如果是异步(async)往非主队列中提交Block,则会在分线程中执行。
2、串行队列/并发队列 和 同步/异步的组合 ( 重点 )
有四种组合方式
1)串行队列 + 同步组合(常用)
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", NULL);
//
dispatch_sync(serialQueue, ^{
NSLog(@"串行队列 + 同步:%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"串行队列 + 同步:%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"串行队列 + 同步:%@",[NSThread currentThread]);
});

说明1:串行队列 (自己创建的串行线程)+ 同步组合下,不会新建线程,依然在当前线程上执行任务。不可以在主线程中使用sync方法,会造成死锁。
说明2:比较常用,同步锁的替代方法。
2)串行队列 + 异步组合
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", NULL);
//
dispatch_async(serialQueue, ^{
NSLog(@"串行队列 + 异步:%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"串行队列 + 异步:%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"串行队列 + 异步:%@",[NSThread currentThread]);
});

说明:串行队列(无论是自己创建的,还是获取主队列) + 异步组合下,会新建线程,但只开启一条线程;
3)并发队列 + 同步组合
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
NSLog(@"并发队列 + 同步1:%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"并发队列 + 同步2:%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"并发队列 + 同步3:%@",[NSThread currentThread]);
});

说明: 并发队列(无论是自己创建的,还是获取全局队列) + 同步组合下,并没有新建线程,任务依然在当前线程上执行。
4)并发队列 + 异步组合(常用)
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"并发队列 + 异步:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"并发队列 + 异步:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"并发队列 + 异步:%@",[NSThread currentThread]);
});

说明:并发队列(无论是自己创建的,还是获取全局队列) + 异步组合下,会新建线程,iOS 系统中可以开多条线程。
同步 | 异步 | |
---|---|---|
串行队列 | 1、不会新建线程,依然在当前线程上执行任务;2、类似同步锁,是同步锁的替代方案 | 1、会新建线程,但只开启一条线程;2、每次使用 dispatch_queue_create创建串行队列,就会创建一条新线程;多次创建,会创建多条线程,多条线程间并发执行。 |
并发队列 | 不会新建线程,依然在当前线程上执行任务 | 1、会新建线程,可以开多条线程;2、iOS7-SDK 时代一般是5、6条, iOS8-SDK 以后可以50、60条 |
总结1:不可以在主线程中使用sync方法,否则会造成死锁。
总结2:串行队列 + 同步组合 可以替代同步锁;
总结3:为了提高效率,如多线程下载图片等,并发队列 + 异步比较常用。
二、GCD使用1:异步处理
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 一个异步的任务,如网络请求,耗时的文件操作等等
...
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新 或其他主线程操作
...
});
});
说明:该用法最常见,如开启一个异步的网络请求,待数据返回后在主线程刷新UI等。
三、GCD使用2:单例
dispatch_once实现单例,以实现QSAccountManager单例为例。源码如下:
1、实现
//QSAccountManager.m
@implementation QSAccountManager
static QSAccountManager *_shareManager = nil;
+ (instancetype)shareManager{
static dispatch_once_t once;
dispatch_once(&once, ^{
_shareManager = [[self alloc] init];
});
return _shareManager;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shareManager = [super allocWithZone:zone];
});
return _shareManager;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone{
return _shareManager;
}
@end
说明1:dispatch_once函数中,参数1是代码块是否被调用的谓词,参数2是被调用的代码块。该函数中的代码块只会被执行一次,而且还是线程安全的。
说明2:要保证单例类只有一个唯一的实例,还需要实现allocWithZone和copyWithZone方法,这保证使用init和copy方法返回也是唯一实例。
2、使用
QSAccountManager *account1 = [QSAccountManager shareManager];
QSAccountManager *account2 = [QSAccountManager new];
QSAccountManager *account3 = [[QSAccountManager alloc]init];
QSAccountManager *account4 = [account3 copy];
NSLog(@"account1 = %@",account1);
NSLog(@"account2 = %@",account2);
NSLog(@"account3 = %@",account3);
NSLog(@"account4 = %@",account4);

四、GCD使用3:代替同步锁
atomic 的内存管理语义是原子性的,仅保证了属性的setter和getter方法是原子性的,但是执行效率低,可以使用GCD实现。
@synchronized(self)同步块机制,会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕,才释放锁。执行效率低。
替代方案:将数据的读取和写放入串行同步队列,保证数据同步,线程安全。
替代方案:结合GCD的栅栏块(barrier)和 并发队列 实现数据同步,线程安全。(比串行同步队列方式更高效)
1、代替atomic实现线程安全的setter和getter方法
//串行队列
_syncQueue = dispatch_queue_create("com.jzp.syncQueue",NULL);
//假设属性是someString
- (NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString {
dispatch_sync(_syncQueue, ^{
_someString = someString;
});
}
2、实现线程安全的NSMutableArray
主要依靠栅栏块单独执行的特性,在并发队列中如果发现接下来要处理的块是个栏栅块,那么就一直等到当前所有并发块都执行完毕,才会单独执行这个栏栅块。待栏栅块执行过后,再按正常方式继续向下处理。
这部分实现详见 iOS实录12:NSMutableArray使用中忽视的问题中“一、线程安全的NSMutableArray”。
说明:dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。
五、GCD使用4:dispatch_group实现线程同步
1、简单模式
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 任务1
});
dispatch_group_async(group, queue, ^{
// 任务2
});
// 等待group中多个异步任务执行完毕,会发出同步信号
// 方式1(会阻塞当前线程,group上任务都完成或超时等待就执行)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// ...
// 方式2(不会阻塞当前线程,group上任务都完成,执行block中代码)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 任务完成后,在主队列中做一些操作
});
说明:将block(任务)放入队列中执行,并和调度组 group相关联;如果提交到dispatch queue中的block全都执行完毕,会执行dispatch_group_notify中的block代码; 或在group上任务完成前,dispatch_group_wait会阻塞当前线程(所以不能放在主线程调用)一直等待;当group上任务完成,或者等待时间超过设置的超时时间会结束等待。
2、多异步任务的同步
成对使用dispatch_group_enter和dispatch_group_leave,可以将异步任务加入group中;
当这些异步任务处理完成后,dispatch_group_notify和dispatch_group_wait会收到同步信号;
异步任务如请求,通过该机制实现批量请求的处理。
dispatch_group_t batch_request_group = dispatch_group_create();
dispatch_group_enter(batch_request_group);
[self.request1 startWithCompleteBlock:^(BOOL isSuccess, id _Nullable responseObj, NSString * _Nonnull errorDesc) {
//TODO 数据解析....
dispatch_group_leave(batch_request_group);
}];
dispatch_group_enter(batch_request_group);
[self.request2 startWithCompleteBlock:^(BOOL isSuccess, id _Nullable responseObj, NSString * _Nonnull errorDesc) {
//TODO 数据解析....
dispatch_group_leave(batch_request_group);
}];
dispatch_group_enter(batch_request_group);
[self.request3 startWithCompleteBlock:^(BOOL isSuccess, id _Nullable responseObj, NSString * _Nonnull errorDesc) {
//TODO 数据解析....
dispatch_group_leave(batch_request_group);
}];
dispatch_group_notify(batch_request_group, dispatch_get_main_queue(), ^{
//三个请求都结束了,继续处理
});
六、GCD使用其他
1、dispatch_apply
按 指定的次数 将指定的Block追加到 指定的Dispatch Queue中, 并等到全部处理执行结束。有并行的运行机制,效率一般快于for循环的类串行机制。
/**
@param 10 指定重复次数,这里指定10次
@param gQueue 追加对象的Dispatch Queue
@param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
*/
dispatch_apply(10, gQueue, ^(size_t index) {
NSLog(@"%zu",index);
});
2、dispatch_after
延迟执行
// NSEC_PER_SEC,每秒有多少纳秒。
// USEC_PER_SEC,每秒有多少毫秒。
// NSEC_PER_USEC,每毫秒有多少纳秒。
// DISPATCH_TIME_NOW 从现在开始
// DISPATCH_TIME_FOREVE 永久
// time为5s
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(5.0 * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_after(time, queue, ^{
// 在queue里面延迟执行的一段代码
// ...
});
3、小心死锁
- 死锁情况1: 在主线程中使用sync方法
dispatch_sync(dispatch_get_main_queue(), ^{
// 任务
...
});
- 死锁情况2: 在串行队列添加同步任务;
// 在串行队列添加同步任务
dispatch_sync(serialQueue, ^{
// 任务
dispatch_sync(serialQueue, ^{
// 任务
});
};
End
作者:南华coder
链接:http://www.jianshu.com/p/e1784f8172c0
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS实录:GCD使用小结(一)的更多相关文章
- iOS多线程 GCD
iOS多线程 GCD Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法. dispatch queue分成以下三种: 1)运行在主线程的Main que ...
- iOS's GCD Note
[iOS's GCD Note] 1.默认有四种全局concureent queue,如下: 通过以下函数来引用: 2.官方文档上并发队列有3种,实际上main就是serial. 1)serial,用 ...
- iOS GCD 编程小结
一.简单介绍 1.GCD简介? 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 2.GCD优势 GCD是苹果公司为多核的并行运算提出的 ...
- ios - GCD简单小结
首先GCD两个名词: 队列 同步异步. 队列: 任务放到队列,队列中的任务执行方式取决于执行队列中任务的方式---同步异步. 串行队列: 任务顺序执行,可以叫阻塞队列.只有前面任务完成才执行后面的. ...
- iOS中GCD的使用小结
http://www.jianshu.com/p/ae786a4cf3b1 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列类型 Th ...
- IOS开发GCD小结
0. Brief Introduction GCD,全称Grand Central Dispath,是苹果开发的一种支持并行操作的机制.它的主要部件是一个FIFO队列和一个线程池,前者用来添加任务,后 ...
- ios多线程-GCD基本用法
ios中多线程有三种,NSTread, NSOperation,GCD 这篇就讲讲GCD的基本用法 平时比较多使用和看到的是: dispatch_async(dispatch_get_global_q ...
- iOS 多线程GCD的基本使用
<iOS多线程简介>中提到:GCD中有2个核心概念:1.任务(执行什么操作)2.队列(用来存放任务) 那么多线程GCD的基本使用有哪些呢? 可以分以下多种情况: 1.异步函数 + 并发队列 ...
- ios学习开发阶段小结
总结一下,开发了1个月10天的ios经验. 先晒成绩单:两个实验性质的app,一个wifi管家,一个图片壁纸软件 技术小结: 1.熟悉基本的各种ns语法:#import,#include,@class ...
随机推荐
- bzoj3638 Cf172 k-Maximum Subsequence Sum
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3638 [题解] 看到k<=20就感觉很py了啊 我们用一棵线段树维护选段的过程,能选到 ...
- 百度之星初赛(A)——T2
数据分割 小w来到百度之星的赛场上,准备开始实现一个程序自动分析系统. 这个程序接受一些形如x_i = x_jxi=xj 或 x_i \neq x_jxi≠xj 的相等/不等约 ...
- 洛谷T8115 毁灭
题目描述 YJC决定对入侵C国的W国军队发动毁灭性打击.将C国看成一个平面直角坐标系,W国一共有n^2个人进入了C国境内,在每一个(x,y)(1≤x,y≤n)上都有恰好一个W国人.YJC决定使用m颗核 ...
- javascript jquery document.ready window.onload
网易 博客 下载LOFTER客户端 注册登录 加关注 凡图的编程之路 2012年7月从一个编程新手的点点滴滴 首页 日志 LOFTER 相册 博友 关于我 日志 关于我 Holy ...
- OpenGL入门学习(三)
http://developer.178.com/201103/94954704639.html 在第二课中,我们学习了如何绘制几何图形,但大家如果多写几个程序,就会发现其实还是有些郁闷之处.例如:点 ...
- linux 内存查看方法:meminfo\maps\smaps\status 文件解析
linux 下面查看内存有多种渠道,比如通过命令 ps ,top,free 等,比如通过/proc系统,一般需要比较详细和精确地知道整机内存/某个进程内存的使用情况,最好通过/proc 系统,下面介绍 ...
- Mac下Lua环境搭建
lua源文件下载安装 到官网安装了lua包,我安装的是 lua-5.3.1 解压之后,命令行cd进入到src目录下,输入make macosx 完成后cd ..到上一层目录, 输入sudo make ...
- WPF+MVVM数据绑定问题集锦
1. 数据绑定的问题 在使用数据绑定时,一般使用 ObservableCollection<T> 类,不使用list列表集合,因为list数据发生变化时,UI界面不更新,而Observa ...
- 新疆大学ACM-ICPC程序设计竞赛五月月赛(同步赛)F 猴子排序的期望【Java/高精度/组合数学+概率论】
链接:https://www.nowcoder.com/acm/contest/116/F 来源:牛客网 题目描述 我们知道有一种神奇的排序方法叫做猴子排序,就是把待排序的数字写在卡片上,然后让猴子把 ...
- Linux漏洞建议工具Linux Exploit Suggester
Linux漏洞建议工具Linux Exploit Suggester 在Linux系统渗透测试中,通常使用Nessus.OpenVAS对目标主机进行扫描,获取目标主机可能存在的漏洞.如果无法进行漏洞 ...