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 ...
随机推荐
- 第1章 SpringBoot 简介
一.什么是Spring Boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发 ...
- centos的python2.6.x升级到python2.7.x方法;python2.6.x的版本就不要用了
python2.6.x的版本,现在使用的很多插件都不支持了.所以如果你的centos还是使用的2.6.x版本,不要犹豫,赶紧升级到2.7.x版本 1.所谓升级,就是再安装一个python2.7.x版本 ...
- 怎样使用1M的内存排序100万个8位数
今天看到这篇文章.颇为震撼.感叹算法之"神通". 借助于合适的算法能够完毕看似不可能的事情. 最早这个问题是在Stack Overflow站点上面给出的(Sorting numbe ...
- HDU 2955 Robberies(01背包变形)
Robberies Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- 未来 5 年八大热门 IT 职业
近日.外媒梳理了未来5年内.也是就是2020年仍将受到热捧的八大科技领域,为IT从业者怎样做好长远规划.有针对性地培养自身技能.又不偏离热门岗位提供了參考. (图片来自网易) 2020年将热门的8大I ...
- Android学习笔记(十)——使用意图链接活动
使用意图链接活动 1.新建一个名为"UsingIntent"的项目,右击src目录下的包名,选择New-->Class选项.并将新的类文件名称命名为"SecondA ...
- Codeforces Round #335 (Div. 2) 606B Testing Robots(模拟)
B. Testing Robots time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...
- Seesion和Cookie详解2
转载来自: https://www.toutiao.com/a6693986851193094664/?tt_from=weixin&utm_campaign=client_share& ...
- Spark 学习笔记:(二)编程指引(Scala版)
参考: http://spark.apache.org/docs/latest/programming-guide.html 后面懒得翻译了,英文记的,以后复习时再翻. 摘要:每个Spark appl ...
- 4408: [Fjoi 2016]神秘数
4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 452 Solved: 273 [Submit][Stat ...