dispatch_barrier_async

2017-03-20  本文已影响0人  魏雷123

在访问数据库或者文件的时候,我们可以使用Serial Dispatch Queue可避免数据竞争问题,代码如下所示:

先看看,如果我们在平常编码中,如果要保证某个属性可以线程安全的读写,如何写的:

#import

@interfaceZYPerson :NSObject

@property(nonatomic,copy)NSString*name;

@end

#import "ZYPerson.h"

staticNSString*_name;

@implementationZYPerson

- (void)setName:(NSString*)name

{

@synchronized(self) {

_name = [namecopy];

}

}

- (NSString*)name

{

@synchronized(self) {

return_name;

}

}

@end

这是我在刚学iOS开发,刚涉及并发中的数据竞争时,书本上提到的一种解决方案。如果有多个线程要执行同一份代码,那么有时候可能会出现问题,这种情况下,通常要使用锁来实现某种同步机制。iOS提供了一种加锁的方式,就是采用内置的synchronization block,也就是上面代码所写的。

这种写法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。执行到这段代码结尾处,锁也就释放了。在上面的例子中,同步行为所针对的对象是self。这么写通常没错,但是@synchronized(self)会大大降低代码效率,甚至很多时候,还可以被人感觉到效率明显下降了,因为共用同一个锁的那些同步块,都必须按顺序执行。若在self对象上频繁加锁,那么程序可能就要等另一段与此无关的代码执行完毕,才可以继续执行当前代码,这样做是很没必要的。

@synchronized(self)会大大降低代码效率,因为所有的同步块(  @synchronized(self)  )都会彼此抢夺同一个锁。要是有多个属性这么写,每个属性的同步块(  @synchronized(self)  )都要等其他所有的同步块执行完毕之后才能执行,这并不是我们想要的结果,我们只想要每个属性各自独立的同步。

还有,不得不说,按上面这么做,虽然可以在一定程度上提供“线程安全”,但却无法保证访问该对象时是绝对线程安全的。事实上,上面的写法,就是atomic,也就是原子性属性xcode自动生成的代码,这种方法,在访问属性时,必定可以从中得到有效值,然而如果在一个线程上多次调用getter方法,每次得到的结果却未必相同,在两次读操作之间,其他线程可能会写入新的属性值。

其实使用GCD可以简单高效的代替同步块或者锁对象,可以使用,串行同步队列,将读操作以及写操作都安排在同一个队列里,即可保证数据同步,代码如下:

#import

@interfaceZYPerson :NSObject

@property(nonatomic,copy)NSString*name;

@end

#import "ZYPerson.h"

@interfaceZYPerson ()

@end

staticNSString*_name;

staticdispatch_queue_t _queue;

@implementationZYPerson

- (instancetype)init

{

if(self= [superinit]) {

_queue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_SERIAL);

}

returnself;

}

- (void)setName:(NSString*)name

{

dispatch_sync(_queue, ^{

_name = [namecopy];

});

}

- (NSString*)name

{

__blockNSString*tempName;

dispatch_sync(_queue, ^{

tempName = _name;

});

returntempName;

}

@end

这样写的思路是:把写操作与读操作都安排在同一个同步串行队列里面执行,这样的话,所有针对属性的访问操作就都同步了。

这种方法的确已经足够好了,但还不是最优的,它只可以实现单读、单写。整体来看,我们最终要解决的问题是,在写的过程中不能被读,以免数据不对,但是读与读之间并没有任何的冲突!

多个getter方法(也就是读取)是可以并发执行的,而getter(读)与setter(写)方法是不能并发执行的,利用这个特点,还能写出更快的代码来,这次注意,不用串行队列,而改用并行队列:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37#import

@interfaceZYPerson :NSObject

@property(nonatomic,copy)NSString*name;

@end

#import "ZYPerson.h"

@interfaceZYPerson ()

@end

staticNSString*_name;

staticdispatch_queue_t _concurrentQueue;

@implementationZYPerson

- (instancetype)init

{

if(self= [superinit]) {

_concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT);

}

returnself;

}

- (void)setName:(NSString*)name

{

dispatch_barrier_async(_concurrentQueue, ^{

_name = [namecopy];

});

}

- (NSString*)name

{

__blockNSString*tempName;

dispatch_sync(_concurrentQueue, ^{

tempName = _name;

});

returntempName;

}

@end

这样优化,测试一下性能,可以发现这种做法肯定比使用串行队列要快。

在这个代码中,我用了点新的东西,dispatch_barrier_async,可以翻译成栅栏(barrier),它可以往队列里面发送任务(块,也就是block),这个任务有栅栏(barrier)的作用。

在队列中,barrier块必须单独执行,不能与其他block并行。这只对并发队列有意义,并发队列如果发现接下来要执行的block是个barrier block,那么就一直要等到当前所有并发的block都执行完毕,才会单独执行这个barrier block代码块,等到这个barrier block执行完毕,再继续正常处理其他并发block。在上面的代码中,setter方法中使用了barrier block以后,对象的读取操作依然是可以并发执行的,但是写入操作就必须单独执行了。

上一篇下一篇

猜你喜欢

热点阅读