使用 GCD 实现属性的多读单写

在这里插入图片描述

使用 Grand Central Dispatch (GCD) 实现多读单写的属性

  1. 首先需要确保在多线程环境下的线程安全性。
  2. 可以使用 GCD 提供的读写锁机制 dispatch_rwlock_t 或者 dispatch_queue_t 来实现这个功能。

Swift版本的实现

  1. 怎样创建一个并发队列 ?
    // 使用 Swift 来实现的首个好处就是:避免使用低等级的 C 语言 API (真的很难用🤣)
    let queue = DispatchQueue(label: "io.sqi.queue.concurrent", attributes: .concurrent)
    
  2. Swift 的属性怎样重写 setter 和 getter ?(是不是 Objective-C 喝多了😂, 应该像下面 3 这样问)
  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))

在这个示例中:

  1. SQIObject 类封装了一个泛型属性,并使用 GCD 的并发队列来确保线程安全。
  2. 读取操作使用 queue.sync 同步读取,以确保多个读取操作可以同时进行。
  3. 写入操作使用 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;
}

在这个示例中:

  1. SQIObject 类封装了一个属性 threadSafeProperty,并使用 GCD 的并发队列 _queue 来确保线程安全。
  2. 读取操作使用 dispatch_sync 同步读取,以确保多个读取操作可以同时进行。
  3. 写入操作使用 dispatch_barrier_async,这确保了在写入操作执行时,所有的读取操作都会被阻塞,直到写入操作完成。这就实现了多读单写的属性。

细节分析

getter 方法为什么这样实现 ?感觉有点怪

- (id)threadSafeProperty {
    __block id result;
    dispatch_sync(_queue, ^{
        result = _threadSafeProperty;
    });
    return result;
}
  1. 首先,想要将读取操作放入队列,必须要有如下片段:

    - (id)threadSafeProperty {
          dispatch_(a)sync(_queue, ^{
              // 读取操作
    	  });
    }
    
    
  2. 不能在 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;
    	  });
    }
    
  3. 所以只能先声明临时变量,然后在 block 中执行 assignment (赋值),完成后,return 出去,结果就是:

    - (id)threadSafeProperty {
        __block id result;
        dispatch_(a)sync(_queue, ^{
            result = _threadSafeProperty;
        });
        return result;
    }
    
    
  4. 而如果想要获取有效的 return 值,GCD Block 中的操作必须 block 线程,在 return 之前完成 assignment, 故只能选择 dispatch_sync,所以最终的结果是:

    - (id)threadSafeProperty {
        __block id result;
        dispatch_sync(_queue, ^{
            result = _threadSafeProperty;
        });
        return result;
    }
    
    

在这里插入图片描述

  • 31
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依旧风轻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值