使用GCD实现多读单写

2,866 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

我们在开发中有时会遇到这种问题,比如在内存中维护一份数据,有多处地方可能会同时操作这块数据,那么如何能保证这块数据的安全?如果要做到这种,需要满足以下三点条件:

  • 1.读写互斥
  • 2.写写互斥
  • 3.读读互斥
@implementation KCPerson
- (instancetype)init{
    if(self = [super init]){
        _concurrentQueue = dispatch_queue_creat("com.kc_person.syncQueue", DISPATCH_QUEUE_CONCURRENT);
        _dic = [NSMutableDictionary dictionary];
    }
    return self;
}
- (void)kc_setSafeObject:(id)object forKey:(NSString *)key{
    key = [key copy];
    dispatch_barrier_async(_concurrentQueue, ^{
        [_dic setObject:object key:key];
    });
}

- (id)kc_safeObjectForKey:(NSString *)key{
    __block NSString *temp;
    dispatch_sync(_concurrentQueue, ^{
        temp = [_dic objcetForKey:key];
    });
    return temp;
}
@end
  • 首先我们要维系一个GCD队列,最好不用全局队列,毕竟大家都知道全局队列遇到栅栏函数是有坑点的,这里就不分析了!(全局队列中,没有对栅栏函数的任何判断和处理。所以,栅栏函数在全局队列中,和普通的同步或者异步函数别无二致

  • 因为考虑性能、死锁、堵塞的因素不考虑穿行队列,用的是自定义的并发队列!_concurrentQueue = dispatch_queue_creat("com.kc_person.syncQueue", DISPATCH_QUEUE_CONCURRENT);

  • 首先我们来看看读操作:kc_safeObjectForKey我们考虑到多线程影响是不能用异步函数的!说明:

    • 线程2获取:name 线程3获取age
    • 如果因为异步并发,导致混乱,本来读的是name,结果读到的是age
    • 我们允许多个任务同时进去!但是读操作需要同步返回,所以我们选择:同步函数(读读并发)
  • 我们再来看看写操作,在写操作的时候对key进行了copy,关于此处的解释,插入一段来自参考文献的引用:

函数调用者可以自由传递一个NSMutableStringkey,并且能够在函数返回后修改它。因此我们必须对传入的字符串使用copy操作以确保函数能够正常的工作。如果传入的字符串不是可变的(也就是正常的NSString类型),调用copy基本上是个空操作。

  • 这里我们选择dispatch_barrier_async,为什么是栅栏函数而不是异步函数或者同步函数,下面分析:
    • 栅栏函数任务:之前所有的任务执行完毕,并且在它后面的任务开始之前,期间不会有其它的任务执行,这样比较好的促使写操作一个接一个写(写写互斥),不会乱!
    • 为什么不是异步函数?异步函数会产生混乱
    • 为什么不用同步函数?如果读写都操作了,那么用同步函数,就有可能存在:我写需要等待读操作回来才能执行,显然是不合理的!