这个 GCD 是否实现了 getter setter 线程安全并且比 @synchronized 工作得更好?对象
is this GCD implemented getter setter thread safe and work better than @synchronized? objc
@interface ViewController ()
@property (nonatomic, strong) NSString *someString;
@end
@implementation ViewController
@synthesize someString = _someString;
- (NSString *)someString {
__block NSString *tmp;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
tmp = _someString;
});
return tmp;
}
- (void)setSomeString:(NSString *)someString {
__block NSString *tmp;
dispatch_barrier_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
tmp = someString;
});
_someString = tmp;
}
@end
有人说它比 @synchronized
更好,因为所有锁定都在 GCD 中处理。
首先,您的 setter 根本没有意义,使用默认的并发队列也可能不是您想要的。您的代码应该看起来更像:
@interface ViewController ()
@property (nonatomic, copy) NSString *someString;
@end
@implementation ViewController
{
dispatch_queue_t _stateGuardQueue;
}
- (instancetype)init
{
if (self = [super init])
{
_stateGuardQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
@synthesize someString = _someString;
- (NSString *)someString {
__block NSString *tmp;
dispatch_sync(_stateGuardQueue, ^{
tmp = _someString;
});
return tmp;
}
- (void)setSomeString:(NSString *)someString {
NSString* tmp = [someString copy];
dispatch_barrier_async(_stateGuardQueue, ^{
_someString = tmp;
});
}
@end
我所做的修改:
- 使 setter 实际上在临界区内进行突变
- 使用私有的、基于实例的并发队列而不是全局默认的并发队列;将障碍块提交到默认并发队列并不会像您认为的那样工作。 (见docs)
- 将
dispatch_barrier_sync
更改为 dispatch_barrier_async
同步等待 setter 块到 return 完全没有意义,因为无法读取过时的内容当前线程。
- 将 属性 更改为具有
copy
语义,这始终是值语义类型(NSString
等)的良好实践。这在 [= =41=] 可能会从多个线程并发读取。
需要知道的是,在孤立的情况下,此模式只提供 "safety" 原子属性,因此可以说您应该只使用那些(更少的代码等)。至于性能问题,是的,对于这种特殊用途,GCD 肯定会优于 @synchronized
。一方面,它允许并发读取,其中 @synchronized
将序列化并发读取。如果不进行测试,我希望原子属性的性能优于两者。也就是说,一般来说,原子属性和以这种方式保护单个操作很少是一种足够的并发策略。
为什么不像下面这样,使用异步方法:
- (NSString *)someString {
__block NSString *tmp;
dispatch_async(_stateGuardQueue, ^{
tmp = _someString;
});
return tmp;
}
@interface ViewController ()
@property (nonatomic, strong) NSString *someString;
@end
@implementation ViewController
@synthesize someString = _someString;
- (NSString *)someString {
__block NSString *tmp;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
tmp = _someString;
});
return tmp;
}
- (void)setSomeString:(NSString *)someString {
__block NSString *tmp;
dispatch_barrier_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
tmp = someString;
});
_someString = tmp;
}
@end
有人说它比 @synchronized
更好,因为所有锁定都在 GCD 中处理。
首先,您的 setter 根本没有意义,使用默认的并发队列也可能不是您想要的。您的代码应该看起来更像:
@interface ViewController ()
@property (nonatomic, copy) NSString *someString;
@end
@implementation ViewController
{
dispatch_queue_t _stateGuardQueue;
}
- (instancetype)init
{
if (self = [super init])
{
_stateGuardQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
@synthesize someString = _someString;
- (NSString *)someString {
__block NSString *tmp;
dispatch_sync(_stateGuardQueue, ^{
tmp = _someString;
});
return tmp;
}
- (void)setSomeString:(NSString *)someString {
NSString* tmp = [someString copy];
dispatch_barrier_async(_stateGuardQueue, ^{
_someString = tmp;
});
}
@end
我所做的修改:
- 使 setter 实际上在临界区内进行突变
- 使用私有的、基于实例的并发队列而不是全局默认的并发队列;将障碍块提交到默认并发队列并不会像您认为的那样工作。 (见docs)
- 将
dispatch_barrier_sync
更改为dispatch_barrier_async
同步等待 setter 块到 return 完全没有意义,因为无法读取过时的内容当前线程。 - 将 属性 更改为具有
copy
语义,这始终是值语义类型(NSString
等)的良好实践。这在 [= =41=] 可能会从多个线程并发读取。
需要知道的是,在孤立的情况下,此模式只提供 "safety" 原子属性,因此可以说您应该只使用那些(更少的代码等)。至于性能问题,是的,对于这种特殊用途,GCD 肯定会优于 @synchronized
。一方面,它允许并发读取,其中 @synchronized
将序列化并发读取。如果不进行测试,我希望原子属性的性能优于两者。也就是说,一般来说,原子属性和以这种方式保护单个操作很少是一种足够的并发策略。
为什么不像下面这样,使用异步方法:
- (NSString *)someString {
__block NSString *tmp;
dispatch_async(_stateGuardQueue, ^{
tmp = _someString;
});
return tmp;
}