作为一个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也是没问题的,也可能会更高效,至于原因上文已经讲到了。在实际使用时,最好还是测一测每种做法的性能,然后从中选出最适合当前场景的方案。

参考:

  1. 《Effective Objective-C 2.0》

GCD的使用(1)使用GCD保护property的更多相关文章

  1. UVA.12716 GCD XOR (暴力枚举 数论GCD)

    UVA.12716 GCD XOR (暴力枚举 数论GCD) 题意分析 题意比较简单,求[1,n]范围内的整数队a,b(a<=b)的个数,使得 gcd(a,b) = a XOR b. 前置技能 ...

  2. iOS边练边学--GCD的基本使用、GCD各种队列、GCD线程间通信、GCD常用函数、GCD迭代以及GCD队列组

    一.GCD的基本使用 <1>GCD简介 什么是GCD 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数   GCD的优势 G ...

  3. 【gcd+stl】UVa1642 Magical GCD

    Description 一个长度为n的数列,选一个连续子序列,使得子序列的公约数*长度最大,求这个最大值.n<=1e5. Solution 连续子序列一般都要用滑动窗口是吧(固定r,快速计算最优 ...

  4. 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 ...

  5. 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 ...

  6. CodeForces990G:GCD Counting(树分治+GCD)

    You are given a tree consisting of nn vertices. A number is written on each vertex; the number on ve ...

  7. IOS - 总结下swift使用GCD 多线程(二)GCD和DispatchQueue

    1.前言  iOS中处理多核并发的技术有两种分别是:`Grand Central Dispatch`(以下简称`GCD`)和`NSOperationQueue`框架.iOS开发的老司机们在程序开发中处 ...

  8. 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!

    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...

  9. [iOS]深入理解GCD

    看到一篇很好的文章,本来想翻译的,但发现已经有人翻译了,遂简单整理了一下,方便阅读学习 新博客[wossoneri.com] 什么是GCD GCD(Grand Central Dispatch)是li ...

随机推荐

  1. Objective C语言中nil、Nil、NULL、NSNull的区别

    以下内容是基于搜集整理的网上资料,供参考. nil:指向Objective C语言中对象的空指针,其定义值为(id)0. Nil:指向Objective C语言中类(Class)的空指针,其定义值为( ...

  2. AC日记——[USACO1.5]数字三角形 Number Triangles 洛谷 P1216

    题目描述 观察下面的数字金字塔. 写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大.每一步可以走到左下方的点也可以到达右下方的点. 7 3 8 8 1 0 2 7 4 4 4 5 ...

  3. 另一篇xtion、kinect选择比较(openni下)

    小小Xtion开箱测评!!2012年03月12日 19:52:55 原文:http://page.renren.com/601107241/note/811764499 ASUS Xtion Pro ...

  4. 深入GCD(五):资源竞争

    概述我将分四步来带大家研究研究程序的并发计算.第一步是基本的串行程序,然后使用GCD把它并行计算化.如果你想顺着步骤来尝试这些程序的话,可以下载源码.注意,别运行imagegcd2.m,这是个反面教材 ...

  5. 【PostgreSQL】安装使用步骤

    1.下载地址 https://www.postgresql.org/download/windows/ 下载按照较新版本,和平台相一致就好 2.安装 选择安装地址 数据存放地址 密码设置 端口使用默认 ...

  6. pycharm索引index时间很长的原因

    pycharm进行索引index的目的时代码自动补全,当引入新的插件时,就会增加索引时间,插件越多,索引时间越长 没有好的解决办法,除非增加硬件:或者不使用代码自动补全功能

  7. linux驱动开发流程

    嵌入式linux驱动开发流程嵌入式系统中,操作系统是通过各种驱动程序来驾驭硬件设备的.设备驱动程序是操作系统内核和硬件设备之间的接口,它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个 ...

  8. 生活娱乐 ATM机键盘余温泄露密码

    安全系统存漏洞 ATM机键盘余温或泄露密码 ATM机会泄露你的银行卡密码? 据美国<大众科学>网站8月30日报道,你的手指在ATM机上留下的余温能让尾随你而来的黑客准确获知你的密码. 加利 ...

  9. Spark MLlib Deep Learning Convolution Neural Network (深度学习-卷积神经网络)3.2

    3.Spark MLlib Deep Learning Convolution Neural Network(深度学习-卷积神经网络)3.2 http://blog.csdn.net/sunbow0 ...

  10. SGU 194 Reactor Cooling 无源汇带上下界可行流

    Reactor Cooling time limit per test: 0.5 sec. memory limit per test: 65536 KB input: standard output ...