美文网首页
dispatch_barrier_async

dispatch_barrier_async

作者: 魏雷123 | 来源:发表于2017-03-20 09:32 被阅读0次

    在访问数据库或者文件的时候,我们可以使用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以后,对象的读取操作依然是可以并发执行的,但是写入操作就必须单独执行了。

    相关文章

      网友评论

          本文标题:dispatch_barrier_async

          本文链接:https://www.haomeiwen.com/subject/jidbnttx.html