您的位置:首页 > 编程语言

写高质量OC代码52建议总结:41.多用派发列队,少用同步锁

2017-07-24 17:48 337 查看
如果有多个线程要执行同一份代码,有时会出问题。对数据的读写时机不可控。通常要使用锁机制来实现同步。有两种办法。
-(void)synchronizedMethod {
@synchronized(self) {
//  safe
}
}
 这种方式会根据给定的对象,自动创建一个锁,等块中的代码执行完成,锁就释放。例子中的对象都是self,这样可以保证每个self的实例对象都可以不受干扰的运行synchronizedMethod。但是,滥用@synchronized(self)会降低代码效率。因为所有的调用都会排队进行。

 另一个方法是使用NSLock对象:
_lock = [[NSLock alloc] init];
-(void)synchronizedMethod {
[_lock lock];
//  Safe
[_lock unlock];
}
 也可以用NSRecursiveLock这种“递归锁”,线程可以多次持有该锁,而不会出现死锁。这两种方式在极端情况下,同步块会导致死锁,其效率也不是很高。如果直接使用锁对象,遇到死锁就会很麻烦。

 替代方案就是使用GCD,比如,属性就是经常需要同步的地方,这种属性要做成“原子的”,用atomic修饰。如果开发者想要自己实现的话:
-(NSSTring *)someString {
@synchronized(self) {
return _someString;
}
}
-(void)setSomeString:(NSString *)someString {
@synchronized(self) {
_someString = someString;
}
}
 这么做虽然可以提供某种程度的线程安全,但是无法保证访问该对象绝对是线程安全的。访问属性的操作确实是原子的。同一个线程上多次调用获取方法,结果未必相同,两次访问中间的写入操作会改变其值。

 使用串行同步队列可保证数据同步。

 
_syncQueue = dispatch_queue_creat("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;
});
}
 把设置操作和获取操作都安排在序列化列队中执行。优化一下,设置方法并不一定必须同步。
-(void)setSomeString:(NSString *)someString {
dispatch_async(_syncQueue, ^{
_someString = someString;
});
}
 这种写法可以提升设置方法的执行速度。但是会使程序性能下降。因为执行异步派发需要拷贝块,如果拷贝所花的时间超过执行所花的时间,这种做法会比原来慢。

 可以将程序改为并发执行。

 
_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 {
dispatch_async(_syncQueue, ^{
_someString = someString;
});
}
 这样暂时还不是同步,所有的操作都会在同一个队列里,由于是并发队列,所有操作可以随意执行。可以用栅栏解决。

 void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

 void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

 并发列队如果发现接下来处理的是个栅栏,就会优先将其他块处理完,然后在单独执行栅栏。在本例中可以对设置方法实现栅栏,属性的读取操作依然可以并发执行,写入操作单独执行。

 
-(void)setSomeString:(NSString *)someString {
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}
 总结:

 1.派发队列可以表述同步语义,比@synchronized和NSLock对象要简单。

 2.将同步和异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而不会阻塞执行异步派发的线程。
 3.使用同步队列及栅栏块,可以令同步行为更加高效。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐