问题引入:在Objective-C中,如果有两个线程执行同一份代码,那么可能会出现问题
传统方案: 用锁来实现同步机制
方法一:同步块 @synchronized(self)
-(void)synchronizedMethod{
@synchronized(self){
//safe
}
}
这种方法会根据给定对象,创建一个锁,并等待块中得代码执行完毕。执行到这段代码结束,锁就释放了。
劣处:@synchronized(self)可以保证每个对象实例都不受干扰的运行其synchronizedMethod方法,但是会降低代码效率,因为共用用一个锁的那些块都必须顺序执行,若是在self对象上频繁枷锁,那么程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码。
方法二:NSlock或者NSRecurisiveLock
_lock = [NSlock alloc] init];
-(void) synchronizedMethod{
[_lock lock];
//safe
[_lock unlock];
}
劣处:在极端情况下,同步块出现死锁,效率也不高
方法三: 属性的原子性
原子性会是编译器在生成setter和getter方法时加上锁定机制,原理如同方法一
-(NSString*) someString{
@synchronized(self)
return _someString;
}
滥用@synchronized(self)会很危险,因为所有同步块都会彼此抢夺同一个锁。要是有很多属性都这么写,那么每个属性的同步块都要等待其他所有同步块执行完毕才能执行。而我们只是想令每个属性各自独立地同步。
所以默认属性使用noatmic可以提高效率
顺便说一下:这么做虽然提供了某种程度的安全,却无法保证访问该对象时绝对线程安全。 当然访问属性操作确实是原子性的。如果在同一个线程上多次调用getter,每次获取到的结果未必相同。 在两次访问之间,其他线程可能会写入新的属性值。
建议使用GCD处理以上提出的问题
方法一:串行同步队列—将对去操作和写入操作都安排在用一个队列里
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);
-(NSString*) someString{
__block NSString* localString;
dispatch_sync(_syncQueue,^{
localString = _someString;
});
return localString
}
-(void)setSomeString:(NSString*)someString {
dispatch_sync(_syncQueue,^{
_someString = someString;
});
}
这样做全部加锁任务都在GCD中处理,而GCD是在相当深的底层来实现的,于是能够做许多优化。
对以上方法做第一步的优化方案
-(void)setSomeString:(NSString*)someString {
dispatch_async(_syncQueue,^{
_someString = someString;
});
}
dispatch_sync --》 dispatch_async
设置方法改成异步分发,从调用者的角度可以提升设置方法的执行速度,而读取操作和写入操作依然是顺序执行的。但这种方法对于块中代码很简洁的情况反而降低了效率,因为执行异步分法时,需要拷贝块若拷贝块所用的时间超过了块的执行时间,那么这样做反而会使效率降低。然而如果块中执行任务比较繁琐,这种该法还是可以提高效率的。 此方法仍然不能解决 “如果在同一个线程上多次调用getter,每次获取到的结果未必相同。 在两次访问之间,其他线程可能会写入新的属性值。”这种情况
为了提高效率还有更优化的方案
多个获取方法可以并行执行,而获取方法和设置方法之间不能并行执行,利用这个特点,可以写出效率更高的代码来。此时更能体现出GCD的优越性
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
-(NSString*) someString{
__block NSString* localString;
dispatch_sync(_syncQueue,^{
localString = _someString;
});
return localString
}
-(void)setSomeString:(NSString*)someString {
dispatch_async(_syncQueue,^{
_someString = someString;
});
}
这样写无法实现同步。所有操作并发执行,所以读取和写入操作随意执行了
GCD中的barrier可以解决以上问题
将setter方法改成如下
-(void)setSomeString:(NSString*)someString {
dispatch_barrier_async(_syncQueue,^{
_someString = someString;
});
}
因为在队列中,barrier块必须单独执行,不能与其他块并行。这支队并行队列有意义。并行队列中如果发现接下来要处理得块是barrier块,那么要等待当前所有并发块都执行完毕才会单独执行这个barrier块。