文章目录
  1. 1. 线程安全
  2. 2. 同步块(synchronization block)
  3. 3. 锁对象
  4. 4. 使用GCD队列实现同步锁
    1. 4.1. GCD队列的概念
    2. 4.2. 使用GCD串行队列来实现同步锁
    3. 4.3. 异步Setter派发
    4. 4.4. 使用GCD栅栏(barrier)优化同步锁

线程安全

在Objective-C中,如果有多个线程要执行同一份代码,那么这时就会出现线程安全问题。可读写的全局变量及静态变量(在 Objective-C 中还包括属性和实例变量)可以在不同线程修改,所以这两者也通常是引起线程安全问题的所在。假如多个线程每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

添加同步锁是避免线程安全问题常用的方式,下面就介绍几种常用的加锁方式,分析去优缺点同时给出一种推荐的加锁方式。

同步块(synchronization block)

下面的代码通过同步块为执行代码加锁

1
2
3
4
5
- (void)synchronizedMethod {    
@synchronized (self) {
//Safe
}
}

这种写法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。执行到这段代码结尾处,锁就释放了。

优点

  • 该同步方法的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制。

缺点

  • 过多的使用@synchronized (self)则会降低代码效率,因为公用同一个锁的那些同步块,都必须按顺序执行。若是在self对象上频繁加锁,程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码,这样效率就低了

还有要注意的一点:@synchronized (self)方法针对self只有一个锁,相当于对于self的所有用到同步块的地方都是公用同一个锁,所以如果有多个同步块,则其他的同步块都要等待当前同步块执行完毕才能继续执行

锁对象

可以为类手动添加一个锁对象,使用锁对象的lock,unlock操作来保证线程安全,下面是简答的代码实现

1
2
3
4
5
6
7
8
@property (nonatomic,strong) NSLock *lock;

_lock = [[NSLock alloc] init];

- (void)synchronizedMethod {
[_lock lock]; //Safe
[_lock unlock];
}

使用锁对象需要特别注意,如果锁使用不当,会出现死锁现象,这时可以使用NSRecursiveLock这种“递归锁”(recursive lock)除了以上锁对象,还有NSConditionLock 条件锁 、NSDistributedLock 分布式锁 ,这些适用于不同的场景。

在使用中,尽量使用NSRecursiveLock,避免多次持有该锁造成死锁。如果保护属性,为每个属性创建一个单独的锁为之服务,不可与别的资源共用,否则问题会变得更复杂

使用GCD队列实现同步锁

我们先来了解下GCD队列的概念,然后通过GCD队列实现一个锁,然后同步GCD barrier block进行优化

GCD队列的概念

GCD的任务派发方式有两种:

  • dispatch_sync() :同步执行,完成了它预定的任务后才返回,阻塞当前线程
  • dispatch_async() :异步执行,会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程

按照任务的执行方式GCD中的队列可一个分为串行队列和并行队列:串行队列 每次只能执行一个任务,并且必须等待前一个执行任务完成;并发队列一次可以并发执行多个任务,不必等待执行中的任务完成。

按照队列的属性可以分为下面三种队列:

  • 主队列:系统创建,在主线程中执行的串行队列,通过dispatch_get_main_queue() 获取
  • 全局队列:系统创建,在子线程中执行的并行队列,通过dispatch_get_global_queue() 获取
  • 用户队列:用户创建,在子线程中执行,串行(使用一个线程)/并行(使用多个线程)都可以,使用dispatch_queue_create()创建

通过下面方式可以创建队列

1
2
3
4
5
6
7
//创建串行队列
dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_create("serial_queue", nil);

//创建并行队列
dispatch_queue_create("paiallel_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_create("paiallel_queue", 0);

使用GCD串行队列来实现同步锁

实现思路是:把setter和getter都安排在序列化的队列里执行,这样的话,所有针对属性的访问就都同步了。为了使代码块能够设置局部变量,getter中用到了__block关键字,若是抛开这一点,这种写法比之前的那些更为整洁。全部加锁任务都在GCD中处理,而GCD是在相当深的底层来实现的,于是能够做许多优化。下面是具体实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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派发

由于Setter操作不需要返回值可以把同步派发改成了异步派发,从调用者的角度来看,这个小改动可以提升设置方法的执行速度(毕竟直接返回而不用等待block执行完成),而读取操作与写入操作依然会按顺序执行。

1
2
3
4
5
6
// setter
- (void)setName:(NSString *)name {
dispatch_async(_serialQueue, ^{
_name = name;
});
}

但是这么改有一个坏处:如果测试一下程序的性能,那么可能发现这种写法比原来慢,因为执行异步派发时,需要拷贝block。若拷贝block所用的时间明显超过执行块所用时间,则这种做法将比原来更慢。

所以,setter的block设置为asynchronous或者synchronous,得看setter的block的复杂度。

使用GCD栅栏(barrier)优化同步锁

在GCD并发队列中栅栏块(barrier block)必须单独执行,不能与其他块并行。在并行队列中,如果发现接下来的要处理的block是barrier block,那么就一直要等当前所有并发块都执行完毕,才会单独执行这个栅栏块。待栅栏块执行完成后,再按正常方式继续向下执行。

下列函数可以向队列中派发块,将其作为栅栏使用:

1
2
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

对于属性操作理想情况是:调用getter可以并发执行,而getter和setter之前不能并发执行。利用GCD barrier_block就可以方便实现。

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
40
@property (nonatomic, copy) NSString *barrierString;
@property (nonatomic, strong) dispatch_queue_t paiallelQueue;

...

_paiallelQueue = dispatch_queue_create("fd_synchrolock.paiallel", DISPATCH_QUEUE_CONCURRENT);

...

- (NSString *)barrierString
{
__block NSString *localBarrierString = nil;

//向并行队列中派发同步任务,会阻塞当前进程
dispatch_sync(_paiallelQueue, ^{
localBarrierString = _barrierString;
});

return localBarrierString;
}

- (void)setBarrierString:(NSString *)barrierString
{
//同步执行
//block执行块简单使用同步派发
dispatch_barrier_sync(_paiallelQueue, ^{
if (_barrierString != barrierString) {
_barrierString = barrierString;
}
});

/* 异步执行
//block执行块复杂使用异步派发(拷贝block所用的时间明显超过执行块所用时间)
dispatch_barrier_async(_paiallelQueue, ^{
if (_barrierString != barrierString) {
_barrierString = barrierString;
}
});
*/

}