第41条:多用派发队列,少用同步锁

在Objective-C中,如果有多个线程要执行同一份代码,那么有时可能会出问题。这种情况下通常要使用锁来实现某种同步机制。在GCD出现之前,有两种方法:

// 使用内置"同步块"
- (void)synchronizedMethod{
    @synchronized(self){
        // Safe
    }
}

// 使用NSLock对象
_lock = [[NSLock alloc] init];

- (void)synchronizedMethod{
    [_lock lock];
    // Safe
    [_lock unlock];
}

这两种方法都很好,但也有缺陷:

  • 在极端情况下,同步块会导致死锁,效率也不是很高
  • 直接用锁对象的话,一旦遇到死锁就会非常麻烦

替代方案就是使用GCD,下面以实现的原子属性的存取方法为例:

// 用同步块实现
- (NSString*)someString{
    @synchronized(self){
        return _someString;
    }
}

- (void)setSomeString:(NSString*)someString{
    @synchronized(self){
        _someString = someString;
    }
}

注意:如果有很多属性都使用了@synchronized(self),那么每个属性的同步块都要等其他所有同步块执行完毕后才能执行,且这样做未必能保证线程安全,如在同一个线程上多次调用getter方法,每次获取到的结果未必相同,在两次访问操作之间,其他线程可能会写入新的属性值。

// 用GCD实现
// 创建一个串行队列
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NUll);

- (NSString*)someString{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

- (void)setSomeString:(NSString*)someString{
    dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}

注意:GCD代码采用的是串行同步队列,将读取操作及写入操作都安排在同一个队列中,可保证数据同步。

进一步优化代码,可以让属性的读取操作都可以并发执行,但是写入操作必须单独执行的情景:

// 创建一个并发队列
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

- (NSString*)someString{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

- (void)setSomeString:(NSString*)someString{
    // 将写入操作放入异步栅栏块中执行
    // 注:barrier表示栅栏,并发队列如果发现接下来需要处理的块为栅栏块,那么就会等待当前并发块都执行完毕后再单独执行栅栏块,执行完栅栏块后再以正常方式继续向下处理。
    dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}

你可能感兴趣的:(第41条:多用派发队列,少用同步锁)