dispatch_barrier_sync 总是死锁
dispatch_barrier_sync always deadlocks
给定以下代码片段:
#import <XCTest/XCTest.h>
@interface DispatchTests : XCTestCase {
dispatch_queue_t _workQueue;
dispatch_queue_t _readWriteQueue;
int _value;
}
-(void)read;
-(void)write;
@end
@implementation DispatchTests
-(void)testDispatch {
_workQueue = dispatch_queue_create("com.work", DISPATCH_QUEUE_CONCURRENT);
_readWriteQueue = dispatch_queue_create("com.readwrite", DISPATCH_QUEUE_CONCURRENT);
_value = 0;
for(int i = 0; i < 100; i++) {
dispatch_async(_workQueue, ^{
if(arc4random() % 4 == 0) {
[self write];
} else {
[self read];
}
});
}
XCTestExpectation* expectation = [self expectationWithDescription:@"dude"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:6.0 handler:nil];
}
-(void)read {
dispatch_sync(_readWriteQueue, ^{
NSLog(@"read:%d", _value);
});
}
-(void)write {
dispatch_barrier_sync(_readWriteQueue, ^{
_value++;
NSLog(@"write:%d", _value);
});
}
@end
这个测试的目的是看看我是否可以使用 dispatch_barrier
来管理 read/write 锁。在这个测试中,reader 和 writer 都是同步的。当我使屏障异步时,测试似乎工作正常,但是我想避免异步行为,因为这个实现很重要。
我想了解为什么 write
方法会死锁。根据 GCD 文档:
When the barrier block reaches the front of a private concurrent
queue, it is not executed immediately. Instead, the queue waits until
its currently executing blocks finish executing. At that point, the
queue executes the barrier block by itself. Any blocks submitted after
the barrier block are not executed until the barrier block completes.
我对 "currently executing blocks" 的意思感到困惑。
我的解释是这样的场景,其中提交了一堆读取 (x),然后是写入 (y),然后是更多读取 (z):
- (x) 执行
- (y) 等到 (x) 完成
- (y) 阻止 (z) 执行
- (x) 完成
- (y) 执行
- (y) 完成
- (z) 执行
- (z) 完成
好的,经过实际测试:您的代码不会阻塞 - 理论上。
但是 - 实际上 - 可能。
您遇到的是所有可用系统线程都已耗尽的情况。为了继续,您的代码需要 GCD 获取一个新线程 - 但不再可用 - 因此,它会死锁。
为了避免这种情况,您需要分析代码以未绑定方式生成新线程的位置。这可能发生在并发队列中,其中块将阻塞或需要很长时间才能完成,并且大量块以高频率提交到该并发队列。
例如,如果你插入一个小延迟:
for(int i = 0; i < 400; i++) {
usleep(1000);
dispatch_async(_workQueue, ^{
if(arc4random() % 4 == 0) {
[self write];
} else {
[self read];
}
});
}
代码可能 运行 直到它定期完成。这当然只是为了演示问题 - 而不是解决您的问题。
给定以下代码片段:
#import <XCTest/XCTest.h>
@interface DispatchTests : XCTestCase {
dispatch_queue_t _workQueue;
dispatch_queue_t _readWriteQueue;
int _value;
}
-(void)read;
-(void)write;
@end
@implementation DispatchTests
-(void)testDispatch {
_workQueue = dispatch_queue_create("com.work", DISPATCH_QUEUE_CONCURRENT);
_readWriteQueue = dispatch_queue_create("com.readwrite", DISPATCH_QUEUE_CONCURRENT);
_value = 0;
for(int i = 0; i < 100; i++) {
dispatch_async(_workQueue, ^{
if(arc4random() % 4 == 0) {
[self write];
} else {
[self read];
}
});
}
XCTestExpectation* expectation = [self expectationWithDescription:@"dude"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:6.0 handler:nil];
}
-(void)read {
dispatch_sync(_readWriteQueue, ^{
NSLog(@"read:%d", _value);
});
}
-(void)write {
dispatch_barrier_sync(_readWriteQueue, ^{
_value++;
NSLog(@"write:%d", _value);
});
}
@end
这个测试的目的是看看我是否可以使用 dispatch_barrier
来管理 read/write 锁。在这个测试中,reader 和 writer 都是同步的。当我使屏障异步时,测试似乎工作正常,但是我想避免异步行为,因为这个实现很重要。
我想了解为什么 write
方法会死锁。根据 GCD 文档:
When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the queue executes the barrier block by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.
我对 "currently executing blocks" 的意思感到困惑。
我的解释是这样的场景,其中提交了一堆读取 (x),然后是写入 (y),然后是更多读取 (z):
- (x) 执行
- (y) 等到 (x) 完成
- (y) 阻止 (z) 执行
- (x) 完成
- (y) 执行
- (y) 完成
- (z) 执行
- (z) 完成
好的,经过实际测试:您的代码不会阻塞 - 理论上。
但是 - 实际上 - 可能。
您遇到的是所有可用系统线程都已耗尽的情况。为了继续,您的代码需要 GCD 获取一个新线程 - 但不再可用 - 因此,它会死锁。
为了避免这种情况,您需要分析代码以未绑定方式生成新线程的位置。这可能发生在并发队列中,其中块将阻塞或需要很长时间才能完成,并且大量块以高频率提交到该并发队列。
例如,如果你插入一个小延迟:
for(int i = 0; i < 400; i++) {
usleep(1000);
dispatch_async(_workQueue, ^{
if(arc4random() % 4 == 0) {
[self write];
} else {
[self read];
}
});
}
代码可能 运行 直到它定期完成。这当然只是为了演示问题 - 而不是解决您的问题。