写高质量OC代码52建议总结:41.多用派发列队,少用同步锁
2017-07-24 17:48
337 查看
如果有多个线程要执行同一份代码,有时会出问题。对数据的读写时机不可控。通常要使用锁机制来实现同步。有两种办法。
另一个方法是使用NSLock对象:
替代方案就是使用GCD,比如,属性就是经常需要同步的地方,这种属性要做成“原子的”,用atomic修饰。如果开发者想要自己实现的话:
使用串行同步队列可保证数据同步。
可以将程序改为并发执行。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
并发列队如果发现接下来处理的是个栅栏,就会优先将其他块处理完,然后在单独执行栅栏。在本例中可以对设置方法实现栅栏,属性的读取操作依然可以并发执行,写入操作单独执行。
1.派发队列可以表述同步语义,比@synchronized和NSLock对象要简单。
2.将同步和异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而不会阻塞执行异步派发的线程。
3.使用同步队列及栅栏块,可以令同步行为更加高效。
-(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.使用同步队列及栅栏块,可以令同步行为更加高效。
相关文章推荐
- 写高质量OC代码52建议总结:29.理解引用计数
- 写高质量OC代码52建议总结:43.掌握GCD及操作队列的使用时机
- 编写高质量OC代码52建议总结:26.不要在分类中设置属性
- 写高质量OC代码52建议总结:39.用handler块降低代码分散程度
- 编写高质量OC代码52建议总结:18.尽量使用不可变对象
- 写高质量OC代码52建议总结:28.通过协议提供匿名对象
- 写高质量OC代码52建议总结:30.以ARC简化引用计数
- 编写高质量OC代码52建议总结:11.理解objc_msgSend的作用(消息机制)
- 写高质量OC代码52建议总结:46.不要使用dispatch_get_current_queue
- 编写高质量OC代码52建议总结:23.通过委托与数据源协议进行对象间通信
- 写高质量OC代码52建议总结:45.使用dispatch_once来执行只需要运行一次的线程安全代码
- 编写高质量OC代码52建议总结:25.总是为第三方类的分类名称加前缀
- 写高质量OC代码52建议总结:33.以弱引用避免保留环
- 编写高质量OC代码52建议总结:27.使用“class-continuation 分类” 隐藏实现细节
- 编写高质量OC代码52建议总结:10.关联对象
- 写高质量OC代码52建议总结:47.熟悉系统架构
- 编写高质量OC代码52建议总结:13.用“方法调配技术”调试“黑盒方法”
- 编写高质量OC代码52建议总结:9.以“族类模式“隐藏实现细节
- 写高质量OC代码52建议总结:48.多用块枚举,少用for循环
- 写高质量OC代码52建议总结:31.在dealloc方法中只释放引用并解除监听