今天重温了 《Effective Objective-C 2.0》,里面有一个章节 多用派发队列,少用同步锁 以前被忽视了,写出来备份下,以下内容为该章节内容抄录和总结。
在Objective-C中,如果有多个线程要执行同一份代码,那么有时可能会出问题,这种情况一般有三种办法:
-
同步块
@synchronized (self) { }
使用 NSLock 或 NSRecursiveLock 等锁对象
使用GCD
相对来说,GCD更加简单而高效。
举个例子,要保证对某一个属性的线程安全,此时使用GCD有两种做法
使用串行队列
_syncqueue = dispatch_queue_create("com.test.gcd", NULL);
- (NSString *)something{
__block NSString *localSomething = nil;
dispatch_sync(_syncqueue, ^{
localSomething = _something;
});
return localSomething;
}
- (void)setSomething:(NSString *)something{
dispatch_sync(_syncqueue, ^{
_something = something;
});
}
此模式把设置操作和读取操作都放到串行队列里执行,所有对属性的访问就都同步了。
使用并发队列
其实,对于属性来说,多个get方法是可以并发执行的,而get方法和set方法之间不能并发执行。而且set方法可以是异步的,因为set方法不需要返回值。
按照这个思路,把队列改成并发队列,,并使用 dispatch_barrier_async
函数,使 get 方法和 set方法 不并发执行。
_syncqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)something{
__block NSString *localSomething = nil;
dispatch_sync(_syncqueue, ^{
localSomething = _something;
});
return localSomething;
}
- (void)setSomething:(NSString *)something{
dispatch_barrier_async(_syncqueue, ^{
_something = something;
});
}
由于set方法中使用了栅栏块,一旦有写入操作时,就会等所有读取的操作全部执行完才会执行写入操作。
同时注意set方法 dispatch_barrier_async
为异步,因为set方法不需要同步,从调用者的角度来讲使用异步会加快执行速度。但是这么做有一个坏处:执行异步派发时,需要拷贝块,如果拷贝快的时间明显超过执行快的所花的时间,则这种做法比使用同步会更慢,但是如果块要执行的为更繁重的任务,那么可以考虑使用异步。对于本文中的例子,只是简单的set,则不需要使用异步派发。
不过这个示例还有点问题,因为使用了全局并发队列,可能会阻塞系统的队列,所以最好使用自定义并发队列。
_syncqueue = dispatch_queue_create("com.test.concurrent", DISPATCH_QUEUE_CONCURRENT);
- (NSString *)something{
__block NSString *localSomething = nil;
dispatch_sync(_syncqueue, ^{
localSomething = _something;
});
return localSomething;
}
- (void)setSomething:(NSString *)something{
dispatch_barrier_async(_syncqueue, ^{
_something = something;
});
}