GCD的使用(1)使用GCD保护property
作为一个iOS开发者,必须要熟练使用GCD,本文是站在实际应用的角度总结GCD的用法之一: 使用barrier保护property。在多线程环境下,如果有多个线程要执行同一份代码,那么有时会出现问题,为了保证某些资源操作的可控性,需要一些手段来对这些“公共资源”进行保护,无论是什么语言,只要支持多线程,都会面对这个问题,即所谓的“资源同步问题”,对于基于Objective-C的iOS开发而言,也许“同步问题”出现最多的资源是property。
atomic修饰词保护property
对property原子性最简单的保护措施是为其添加atomic修饰词,被atomic修饰的property,其setter方法默认为:
譬如:
@property (atomic, strong) NSString *name;
加了atomic,setter函数会变成下面这样:
- (void)setName:(NSString *)newName {
{lock}
if (_name != newName) {
_name = newName;
}
{unlock}
}
P.S:这段代码是参考网友的说法,并不权威。
总之,借用网友的描述:
如果使用atomic,如其名,它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。但是,我上网查了资料,仅仅靠atomic来保证线程安全是很天真的。
此外,根据《Effective Objective-C 2.0》模糊的描述,似乎LLVM为atomic修饰的property生成的setter和getter似乎如下:
@property (atomic, strong) NSString *someString; @synthesize someString = _someString; // getter
- (NSString *)someString {
@synchronized(self) {
return _someString;
}
} // setter
- (void)setSomeString:(NSString *)someString {
@synchronized(self) {
_someString = someString;
}
}
如果果真是这样,那么使用atomic维持property的getter和setter的线程安全是有问题的,有什么问题呢?如果self中有很多个使用atomic修饰的property,譬如self同时有两个被atomic修饰的property:property1和property2,那么意味着如果线程A正在访问property1的时候线程B想访问property2,则会被阻塞,直到线程A结束对property1的访问。简而言之,self的所有被atomic修饰的资源都共用一把“锁”,这显然比较呆笨。
可见,滥用@synchronized(self)会降低代码效率,因为共用同一把锁的那些同步块,都必须按顺序执行。所以网友说“仅仅靠atomic来保证线程安全是很天真的”。
NSLock保护property
使用NSLock也是可以保护property的,但似乎只能用户保护property的setter方法,并且,我总觉得@synchronized(self)的本质和使用NSLock是差不多的。
如下:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSLock *lockForName; - (void)setName:(NSString *)name {
[self.lockForName lock];
_name = name;
[self.lockForName unlock];
}
总之一句话,使用NSLock实现property的线程安全,不靠谱!
serial dispatch queue保护property
将对property的读写方法都安排在同一个队列中,即可保证数据同步,如下:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) dispatch_queue_t serialQueue; @synthesize name = _name; // create a serial dispatch queue
_serialQueue = dispatch_queue_create("com.zhangbuhuai.test", nil); // getter
- (NSString *)name {
__block NSString *localName;
dispatch_sync(_serialQueue, ^{
localName = _name;
});
return localName;
} // setter
- (void)setName:(NSString *)name {
dispatch_sync(_serialQueue, ^{
_name = name;
});
}
此模式的思路是:把setter和getter都安排在序列化的队列里执行,这样的话,所有针对属性的访问就都同步了。为了使代码块能够设置局部变量,getter中用到了__block语句,若是抛开这一点,这种写法比之前的那些更为整洁。全部加锁任务都在GCD中处理,而GCD是在相当深的底层来实现的,于是能够做许多优化。因此,开发者无需担心那些事,只要专心把访问方法写好就行了。
然而,还可以进一步优化,毕竟setter方法不一定非得是同步的。设置实例变量所用的block,并不需要向setter返回什么值。也就是说,setter代码可以改成下面这样:
// setter
- (void)setName:(NSString *)name {
dispatch_async(_serialQueue, ^{
_name = name;
});
}
这次只是把同步派发改成了异步派发,从调用者的角度来看,这个小改动可以提升设置方法的执行速度(毕竟直接返回而不用等待block执行完成),而读取操作与写入操作依然会按顺序执行。但是这么改有一个坏处:如果测试一下程序的性能,那么可能发现这种写法比原来慢,因为执行异步派发时,需要拷贝block。若拷贝block所用的时间明显超过执行块所用时间,则这种做法将比原来更慢。
P.S:所以,setter的block设置为asynchronous或者synchronous全看setter的block的复杂度。
barrier保护property
其实在更多的时候,调用getter可以并发执行,而getter和setter之前不能并发执行。利用这个特点,还能写一些更快一些的代码。此时正可以体现出GCD写法的好处。用同步块或锁对象,是无法轻易实现出如下这种方案的,这次不用serial dispatch queue,而改用并发队列:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) dispatch_queue_t concurrentQueue; @synthesize name = _name; // create a concurrent dispatch queue
_concurrentQueue = dispatch_queue_create("com.zhangbuhuai.test", ); // getter
- (NSString *)name {
__block NSString *localName;
dispatch_sync(_concurrentQueue, ^{
localName = _name;
});
return localName;
} // setter
- (void)setName:(NSString *)name {
dispatch_async(_concurrentQueue, ^{
_name = name;
});
}
然而,如上这样的代码,还无法正确实现同步。所有读取操作与写入操作都会在同一个队列上执行,不过由于是并发队列,所以读取与写入操作可能随时执行。而我们恰恰不想让这些操作随意执行。此问题用一个简单的GCD功能即可解决,它就是栅栏(barrier)。下列函数可以向队列中派发块,将其作为栅栏使用:
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
在队列中,栅栏块必须单独执行,不能与其他块并行。这只对并发队列有意义,因为串行队列中的块总是按顺序逐个来执行的。并发队列如果发现接下来的要处理的block是barrier block,那么就一直要等当前所有并发块都执行完毕,才会单独执行这个栅栏块。待栅栏块执行完成后,再按正常方式继续向下执行。
在本例中,可以用栅栏块来实现属性的setter方法。在设置方法中使用了栅栏块之后,对属性的读取操作依然可以并发执行,但是写入操作却必须单独执行了,如下图所示:

代码实现很简单:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) dispatch_queue_t concurrentQueue; @synthesize name = _name; // create a concurrent dispatch queue
_concurrentQueue = dispatch_queue_create("com.zhangbuhuai.test", ); // getter
- (NSString *)name {
__block NSString *localName;
dispatch_sync(_concurrentQueue, ^{
localName = _name;
});
return localName;
} // setter
- (void)setName:(NSString *)name {
dispatch_barrier_async(_concurrentQueue, ^{
_name = name;
});
}
测试一下性能,就会发现,这种做法肯定比使用串行队列要快。当然,将上述代码中的dispatch_barrier_async改为dispatch_barrier_sync也是没问题的,也可能会更高效,至于原因上文已经讲到了。在实际使用时,最好还是测一测每种做法的性能,然后从中选出最适合当前场景的方案。
参考:
- 《Effective Objective-C 2.0》
GCD的使用(1)使用GCD保护property的更多相关文章
- UVA.12716 GCD XOR (暴力枚举 数论GCD)
UVA.12716 GCD XOR (暴力枚举 数论GCD) 题意分析 题意比较简单,求[1,n]范围内的整数队a,b(a<=b)的个数,使得 gcd(a,b) = a XOR b. 前置技能 ...
- iOS边练边学--GCD的基本使用、GCD各种队列、GCD线程间通信、GCD常用函数、GCD迭代以及GCD队列组
一.GCD的基本使用 <1>GCD简介 什么是GCD 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 GCD的优势 G ...
- 【gcd+stl】UVa1642 Magical GCD
Description 一个长度为n的数列,选一个连续子序列,使得子序列的公约数*长度最大,求这个最大值.n<=1e5. Solution 连续子序列一般都要用滑动窗口是吧(固定r,快速计算最优 ...
- Codeforces 798C. Mike and gcd problem 模拟构造 数组gcd大于1
C. Mike and gcd problem time limit per test: 2 seconds memory limit per test: 256 megabytes input: s ...
- HDU - 4676 :Sum Of Gcd (莫队&区间gcd公式)
Given you a sequence of number a 1, a 2, ..., a n, which is a permutation of 1...n. You need to answ ...
- CodeForces990G:GCD Counting(树分治+GCD)
You are given a tree consisting of nn vertices. A number is written on each vertex; the number on ve ...
- IOS - 总结下swift使用GCD 多线程(二)GCD和DispatchQueue
1.前言 iOS中处理多核并发的技术有两种分别是:`Grand Central Dispatch`(以下简称`GCD`)和`NSOperationQueue`框架.iOS开发的老司机们在程序开发中处 ...
- 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...
- [iOS]深入理解GCD
看到一篇很好的文章,本来想翻译的,但发现已经有人翻译了,遂简单整理了一下,方便阅读学习 新博客[wossoneri.com] 什么是GCD GCD(Grand Central Dispatch)是li ...
随机推荐
- HDU - 5572 An Easy Physics Problem (计算几何模板)
[题目概述] On an infinite smooth table, there's a big round fixed cylinder and a little ball whose volum ...
- Jetson TK1 一:调整屏幕分辨率
先点击分辨率调整的那个对话框,就是1024×768之类的那个,选定一个分辨率,我选的是1366x768(16:9)的那个,然后连续按7下tab按键,然后回车,然后再点击弹出的选项右边一个就能保存了.
- hadoop+yarn+hbase+storm+kafka+spark+zookeeper)高可用集群详细配置
配置 hadoop+yarn+hbase+storm+kafka+spark+zookeeper 高可用集群,同时安装相关组建:JDK,MySQL,Hive,Flume 文章目录 环境介绍 节点介绍 ...
- C# 读写bat文件
读: var batFile = "D:\\test.bat"; if (File.Exists(batFile)) { using (var sr = new StreamRea ...
- PHP实现INT型,SHORT型,STRING转换成BYTE数组
实现PHP实现INT型,SHORT型,STRING转换成BYTE数组的转化: class Bytes { public static function integerToBytes($val) { $ ...
- AppCompatActivity
刚开始看HelloWorld的目录结构然后就发现Android Studio中的是 import android support.v7.app.AppcompatActivity; public cl ...
- BUPT复试专题—C翻转(2010)
https://www.nowcoder.com/practice/74bdb725421c4f80b4aca7266818baf0?tpId=67&tqId=29639&rp=0&a ...
- IE8.0登录Oracle EBS后报Oracle error 1403错
IE8.0登录Oracle EBS后报错,登录页面打开没有问题.只是输入username和password然后登录,遇到下面错误: <PRE>Oracle error 1403: java ...
- Html5培训之精髓
一.核心技术(可去各技术官网学习) 1.html5的六大核心技术:Html5,CSS3,JavaScript,WebSocket,PhoneGap,Node.js,它们覆盖了设备端,浏览器端和云端的开 ...
- 【转载】win7下IIS的安装和配置图文教程
1. 首先是安装IIS.打开控制面板,找到"程序与功能",点进去 2. 点击左侧"打开或关闭Windows功能" 3. 找到"Internet 信息服 ...