Ios 我编写的由GCD代码支持的读写器锁在并行测试中导致死锁

Ios 我编写的由GCD代码支持的读写器锁在并行测试中导致死锁,ios,concurrency,grand-central-dispatch,deadlock,readerwriterlock,Ios,Concurrency,Grand Central Dispatch,Deadlock,Readerwriterlock,我在GCD中实现了这个读写器锁,但在并行测试中失败了。我能得到它为什么失败的解释吗 这是用于iOS开发的。代码基于Objective C。我在GCD中编写了一个带有读写器锁的RWCache,用于数据保护 @interface RWCache : NSObject - (void)setObject:(id)object forKey:(id <NSCopying>)key; - (id)objectForKey:(id <NSCopying>)key; @end

我在GCD中实现了这个读写器锁,但在并行测试中失败了。我能得到它为什么失败的解释吗

这是用于iOS开发的。代码基于Objective C。我在GCD中编写了一个带有读写器锁的RWCache,用于数据保护

@interface RWCache : NSObject

- (void)setObject:(id)object forKey:(id <NSCopying>)key;

- (id)objectForKey:(id <NSCopying>)key;

@end

@interface RWCache()
@property (nonatomic, strong) NSMutableDictionary *memoryStorage;
@property (nonatomic, strong) dispatch_queue_t storageQueue;
@end

@implementation RWCache

- (instancetype)init {
    self = [super init];
    if (self) {
        _memoryStorage = [NSMutableDictionary new];
        _storageQueue = dispatch_queue_create("Storage Queue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (void)setObject:(id)object forKey:(id <NSCopying>)key {
    dispatch_barrier_async(self.storageQueue, ^{
        self.memoryStorage[key] = object;
    });
}

- (id)objectForKey:(id <NSCopying>)key {
    __block id object = nil;
    dispatch_sync(self.storageQueue, ^{
        object = self.memoryStorage[key];
    });
    return object;
}

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RWCache *cache = [RWCache new];
        dispatch_queue_t testQueue = dispatch_queue_create("Test Queue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group = dispatch_group_create();
        for (int i = 0; i < 100; i++) {
            dispatch_group_async(group, testQueue, ^{
                [cache setObject:@(i) forKey:@(i)];
            });
            dispatch_group_async(group, testQueue, ^{
                [cache objectForKey:@(i)];
            });
        }
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    }
    return 0;
}

@接口RWCache:NSObject
-(void)setObject:(id)object forKey:(id)key;
-(id)objectForKey:(id)key;
@结束
@接口RWCache()
@属性(非原子,强)NSMutableDictionary*memoryStorage;
@属性(非原子,强)调度队列存储队列;
@结束
@RWCache的实现
-(instancetype)初始化{
self=[super init];
如果(自我){
_memoryStorage=[NSMutableDictionary new];
_storageQueue=调度队列创建(“存储队列”,调度队列并发);
}
回归自我;
}
-(void)setObject:(id)object forKey:(id)键{
dispatch\u barrier\u async(self.storageQueue^{
self.memoryStorage[key]=对象;
});
}
-(id)objectForKey:(id)键{
__块id对象=nil;
调度同步(self.storageQueue^{
object=self.memoryStorage[key];
});
返回对象;
}
@结束
int main(int argc,const char*argv[]{
@自动释放池{
RWCache*cache=[RWCache new];
调度队列测试队列=调度队列创建(“测试队列”,调度队列并发);
dispatch_group_t group=dispatch_group_create();
对于(int i=0;i<100;i++){
调度组异步(组、测试队列、^{
[cache setObject:@(i)forKey:@(i)];
});
调度组异步(组、测试队列、^{
[cache objectForKey:@(i)];
});
}
调度组等待(组、调度时间永远);
}
返回0;
}

如果没有死锁,程序将退出0,否则程序将挂起而不退出。

问题不在于读写器模式本身,而在于此代码中的一般线程爆炸。请参阅WWDC 2015视频中的“线程爆炸导致死锁”讨论。2016年WWDC也是一个很好的视频。在这两个链接中,我将在视频的相关部分向您介绍,但这两个链接都值得完整观看

总之,您正在耗尽GCD线程池中数量非常有限的工作线程。只有64个。但是您有100个带屏障的写操作,这意味着在该队列上的所有其他操作完成之前,已调度的块无法运行。这些线程中穿插着100次读取,由于它们是同步的,因此将阻止从中调度它的工作线程,直到它返回

让我们将其简化为更简单的问题:

dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 100; i++) {
    dispatch_async(queue2, ^{
        dispatch_barrier_async(queue1, ^{
            NSLog(@"barrier async %d", i);
        });
        dispatch_sync(queue1, ^{
            NSLog(@"sync %d", i);
        });
    });
}
NSLog(@"done dispatching all blocks to queue1");
或者,另一种方法是使用
dispatch\u apply
,它有效地实现了
for
循环的并行化,但将任何给定时刻的并发任务数限制为机器上的内核数(使我们大大低于耗尽工作线程的阈值):


我不完全清楚为什么您的代码会死锁,但这似乎是一种可笑的共享字典访问权限的复杂方式。一个简单的
NSLock
就可以了。。。效率提高100倍。@JamesBucanek你说得对。这可能是没有效率的。我只是尝试在GCD中实现读写器锁,看看它是如何工作的,我还想知道为什么它会导致这个演示的死锁。我不会认为它效率较低。对其进行基准测试。上次我对它进行基准测试时,reader writer的速度要快得多…@Rob,很公平。但我想区分快速和高效。我承认,在一些极端的边缘情况下,对于这样的
O(1)
操作,这样的读写方案可能运行得更快,但效率不可能更高。我的证明是,读操作将涉及至少两个线程开关,加上由(可能是多个)信号量和屏障操作控制的助理队列和块的解队列。这不可能打败单个(可能是无竞争的)信号量。(“有效”定义为每瓦特完成的功)@Rob侧栏:对于OP来说,这是一个练习,很好。我的观点是关于现实世界的应用程序,特别是针对移动设备的应用程序,尽管我认为我们桌面开发人员也应该在提高软件效率方面尽自己的一份力量。在看到你的答案之前,我有自己的猜测并写了出来。这太棒了。请让我知道我的假设是否和你的假设一样。是的,同样的想法,尽管我可能会反驳,因为我认为任何讨论都不完整,不讨论“工作线程”和GCD的“工作线程池”。这是GCD施加的约束(尽管并非完全不合理)。我会让你看录像。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(30);

for (int i = 0; i < 100; i++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue2, ^{
        dispatch_barrier_async(queue1, ^{
            NSLog(@"barrier async %d", i);
        });
        dispatch_sync(queue1, ^{
            NSLog(@"sync %d", i);
        });
        dispatch_semaphore_signal(semaphore);
    });
}
NSLog(@"done dispatching all blocks to queue1");
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

dispatch_apply(100, queue2, ^(size_t i) {
    dispatch_barrier_async(queue1, ^{
        NSLog(@"barrier async %ld", i);
    });
    dispatch_sync(queue1, ^{
        NSLog(@"sync %ld", i);
    });
});
NSLog(@"done dispatching all blocks to queue1");