使用 Grand Central Dispatch (GCD) 实现多读单写的属性
- 首先需要确保在多线程环境下的线程安全性。
- 可以使用 GCD 提供的读写锁机制
dispatch_rwlock_t
或者dispatch_queue_t
来实现这个功能。
Swift版本的实现
- 怎样创建一个并发队列 ?
// 使用 Swift 来实现的首个好处就是:避免使用低等级的 C 语言 API (真的很难用🤣) let queue = DispatchQueue(label: "io.sqi.queue.concurrent", attributes: .concurrent)
Swift 的属性怎样重写 setter 和 getter ?(是不是 Objective-C 喝多了😂, 应该像下面 3 这样问)- 应该使用什么类型的属性,setter 和 getter 怎样实现 ?
使用计算属性,setter 使用
set { }
, 注意不是didSet { }
, getter 使用get { }
import Foundation
class SQIObject<T> {
private var _threadSafeProperty: T
private let queue = DispatchQueue(label: "io.sqi.threadSafeProperty", attributes: .concurrent)
init(threadSafeProperty: T) {
self._threadSafeProperty = threadSafeProperty
}
var threadSafeProperty: T {
get {
return queue.sync {
return _threadSafeProperty
}
}
set {
queue.async(flags: .barrier) {
// 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue
self._threadSafeProperty = newValue
}
}
}
}
// 示例使用
let demo = SQIObject(threadSafeProperty: 0)
// 多读示例
DispatchQueue.concurrentPerform(iterations: 10) { index in
print("Read \(index): \(demo.threadSafeProperty)")
}
// 单写示例
DispatchQueue.global().async {
demo.threadSafeProperty = 42
print("ThreadSafeProperty updated to 42")
}
// 确保程序不会立即退出
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
print("Final threadSafeProperty: \(demo.threadSafeProperty)")
}
RunLoop.main.run(until: Date(timeIntervalSinceNow: 2))
在这个示例中:
SQIObject
类封装了一个泛型属性,并使用 GCD 的并发队列来确保线程安全。- 读取操作使用
queue.sync
同步读取,以确保多个读取操作可以同时进行。 - 写入操作使用
queue.async(flags: .barrier)
,这确保了在写入操作执行时,所有的读取操作都会被阻塞,直到写入操作完成。这就实现了多读单写的属性。
Objective-C 版本的实现
利用 Grand Central Dispatch (GCD) 中的并发队列和屏障块来确保线程安全。
#import <Foundation/Foundation.h>
@interface SQIObject : NSObject
// 一个线程安全的多读单写属性
@property (nonatomic, strong) id threadSafeProperty;
- (instancetype)initWithThreadSafeProperty:(id)threadSafeProperty;
@end
@implementation SQIObject {
id _threadSafeProperty;
dispatch_queue_t _queue;
}
- (instancetype)initWithThreadSafeProperty:(id)threadSafeProperty {
self = [super init];
if (self) {
_threadSafeProperty = threadSafeProperty;
_queue = dispatch_queue_create("io.sqi.queue.concurrent", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (id)threadSafeProperty {
__block id result;
dispatch_sync(_queue, ^{
result = _threadSafeProperty;
});
return result;
}
- (void)setThreadSafeProperty:(id)threadSafeProperty {
dispatch_barrier_async(_queue, ^{
_threadSafeProperty = threadSafeProperty;
});
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SQIObject *object = [[SQIObject alloc] initWithThreadSafeProperty:@0];
// 多读示例
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"Read %zu: %@", index, [object threadSafeProperty]);
});
// 单写示例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[SQIObject setThreadSafeProperty:@42];
NSLog(@"ThreadSafeProperty updated to 42");
});
// 确保程序不会立即退出
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Final threadSafeProperty: %@", [object threadSafeProperty]);
CFRunLoopStop(CFRunLoopGetMain());
});
CFRunLoopRun();
}
return 0;
}
在这个示例中:
SQIObject
类封装了一个属性threadSafeProperty
,并使用 GCD 的并发队列_queue
来确保线程安全。- 读取操作使用
dispatch_sync
同步读取,以确保多个读取操作可以同时进行。 - 写入操作使用
dispatch_barrier_async
,这确保了在写入操作执行时,所有的读取操作都会被阻塞,直到写入操作完成。这就实现了多读单写的属性。
细节分析
getter 方法为什么这样实现 ?感觉有点怪
- (id)threadSafeProperty {
__block id result;
dispatch_sync(_queue, ^{
result = _threadSafeProperty;
});
return result;
}
-
首先,想要将读取操作放入队列,必须要有如下片段:
- (id)threadSafeProperty { dispatch_(a)sync(_queue, ^{ // 读取操作 }); }
-
不能在 block 内部直接
return
, 会报类型不匹配// Incompatible block pointer types passing 'id (^)(void)' to parameter of type 'dispatch_block_t _Nonnull' (aka 'void (^)(void)') - (id)threadSafeProperty { dispatch_(a)sync(_queue, ^{ return _threadSafeProperty; }); }
-
所以只能先声明临时变量,然后在 block 中执行
assignment (赋值)
,完成后,return 出去,结果就是:- (id)threadSafeProperty { __block id result; dispatch_(a)sync(_queue, ^{ result = _threadSafeProperty; }); return result; }
-
而如果想要获取有效的 return 值,GCD Block 中的操作必须 block 线程,在 return 之前完成 assignment, 故只能选择
dispatch_sync
,所以最终的结果是:- (id)threadSafeProperty { __block id result; dispatch_sync(_queue, ^{ result = _threadSafeProperty; }); return result; }