在Objective-C中,如果有多个线程要执行同一份代码,那么有时可能会出问题。这种情况下通常要使用锁来实现某种同步机制。在GCD出现之前,有两种方法:
// 使用内置"同步块"
- (void)synchronizedMethod{
@synchronized(self){
// Safe
}
}
// 使用NSLock对象
_lock = [[NSLock alloc] init];
- (void)synchronizedMethod{
[_lock lock];
// Safe
[_lock unlock];
}
这两种方法都很好,但也有缺陷:
- 在极端情况下,同步块会导致死锁,效率也不是很高
- 直接用锁对象的话,一旦遇到死锁就会非常麻烦
替代方案就是使用GCD,下面以实现的原子属性的存取方法为例:
// 用同步块实现
- (NSString*)someString{
@synchronized(self){
return _someString;
}
}
- (void)setSomeString:(NSString*)someString{
@synchronized(self){
_someString = someString;
}
}
注意:如果有很多属性都使用了@synchronized(self),那么每个属性的同步块都要等其他所有同步块执行完毕后才能执行,且这样做未必能保证线程安全,如在同一个线程上多次调用getter方法,每次获取到的结果未必相同,在两次访问操作之间,其他线程可能会写入新的属性值。
// 用GCD实现
// 创建一个串行队列
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NUll);
- (NSString*)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString*)someString{
dispatch_sync(_syncQueue, ^{
_someString = someString;
});
}
注意:GCD代码采用的是串行同步队列,将读取操作及写入操作都安排在同一个队列中,可保证数据同步。
进一步优化代码,可以让属性的读取操作都可以并发执行,但是写入操作必须单独执行的情景:
// 创建一个并发队列
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString*)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString*)someString{
// 将写入操作放入异步栅栏块中执行
// 注:barrier表示栅栏,并发队列如果发现接下来需要处理的块为栅栏块,那么就会等待当前并发块都执行完毕后再单独执行栅栏块,执行完栅栏块后再以正常方式继续向下处理。
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}