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 ...
随机推荐
- 在Fedora 22下安装配置RealVNC Server 5.2.3的经验总结
RealVNC是目前功能最全.性能最好的VNC商业软件套件,很多时候为了确保性能和功能的统一,还是大量地在使用RealVNC.最近在Fedora 22工作站上安装RealVNC Server 5.2. ...
- 王垠 :写给支持和反对《完全用Linux工作》的人们 ( 2004/8/26)
在一阵阵唾骂和欢呼声中,<完全用linux工作>被转载到了天涯海角.很多人支持我,也有很多人唾骂我.我不知道它是怎样流传到那么多地方,其实 我不知道它都被转到哪里去了…… 我知道现在这篇文 ...
- InitializingBean
org.springframework.beans.factory包下有一个接口是InitializingBean 只有一个方法: /** * Invoked by a BeanFactory af ...
- T2832 6个朋友 codevs
http://codevs.cn/problem/2832/ 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 有这么一种说法: ...
- eclispe集成web插件
最近公司需要使用开源框架开发,所有下载了最新版本的eclispe工具,但是在官网下载的eclispe是不包含web插件的,无法创建web项目,需要自行集成web插件 eclipse官网下载地址:htt ...
- 通过BSSID和无线流量传输后门Payload
本文将探讨无线接入点(AP)和BSSID(MAC地址AP).我们不借助文件系统加密和文件系统中(仅内存中)的硬编码Payload即可获得后门Payload,通过该方法可绕过所有的杀软,可以不使用Pay ...
- 【转】 nginx重定向规则详细介绍
rewrite命令 nginx的rewrite相当于apache的rewriterule(大多数情况下可以把原有apache的rewrite规则加上引号就可以直接使用),它可以用在server,loc ...
- poj 2135 Farm Tour 最小费最大流
inf开太小错了好久--下次还是要用0x7fffffff #include<stdio.h> #include<string.h> #include<vector> ...
- BUPT复试专题—数据库检索(2014软院)
题目描述 在数据库的操作过程中,我们进场会遇到检索操作.这个题目的任务是完成一些特定格式的检索,并输出符合条件的数据库中的所有结果. 我们现在有一个数据库,维护了学生的姓名(Name),性别(Sex) ...
- BUPT复试专题—哈夫曼树(2010)
https://www.nowcoder.com/practice/162753046d5f47c7aac01a5b2fcda155?tpId=67&tqId=29635&tPage= ...