dispatch_barrier_async

在访问数据库或者文件的时候,我们可以使用Serial Dispatch Queue可避免数据竞争问题,代码如下所示:
先看看,如果我们在平常编码中,如果要保证某个属性可以线程安全的读写,如何写的:
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
#import <Foundation/Foundation.h>
 
@interface  ZYPerson :  NSObject
@property  ( nonatomic copy NSString  *name;
@end
 
 
#import "ZYPerson.h"
 
static  NSString  *_name;
@implementation  ZYPerson
- ( void )setName:( NSString  *)name
{
     @synchronized ( self ) {
         _name = [name  copy ];
     }
}
 
- ( 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可以简单高效的代替同步块或者锁对象,可以使用,串行同步队列,将读操作以及写操作都安排在同一个队列里,即可保证数据同步,代码如下:

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
38
39
#import <Foundation/Foundation.h>
 
@interface  ZYPerson :  NSObject
@property  ( nonatomic copy NSString  *name;
@end
 
 
#import "ZYPerson.h"
 
@interface  ZYPerson ()
@end
 
static  NSString  *_name;
static  dispatch_queue_t _queue;
@implementation  ZYPerson
- (instancetype)init
{
     if  ( self  = [ super  init]) {
        _queue = dispatch_queue_create( "com.person.syncQueue" , DISPATCH_QUEUE_SERIAL);
     }
     return  self ;
}
 
- ( void )setName:( NSString  *)name
{
     dispatch_sync(_queue, ^{
         _name = [name  copy ];
     });
}
 
- ( NSString  *)name
{
     __block  NSString  *tempName;
     dispatch_sync(_queue, ^{
         tempName = _name;
     });
     return  tempName;
}
@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 <Foundation/Foundation.h>
 
@interface  ZYPerson :  NSObject
@property  ( nonatomic copy NSString  *name;
@end
 
 
#import "ZYPerson.h"
 
@interface  ZYPerson ()
@end
 
static  NSString  *_name;
static  dispatch_queue_t _concurrentQueue;
@implementation  ZYPerson
- (instancetype)init
{
     if  ( self  = [ super  init]) {
        _concurrentQueue = dispatch_queue_create( "com.person.syncQueue" , DISPATCH_QUEUE_CONCURRENT);
     }
     return  self ;
}
- ( void )setName:( NSString  *)name
{
     dispatch_barrier_async(_concurrentQueue, ^{
         _name = [name  copy ];
     });
}
- ( NSString  *)name
{
     __block  NSString  *tempName;
     dispatch_sync(_concurrentQueue, ^{
         tempName = _name;
     });
     return  tempName;
}
@end

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值