2016-04-14 51 views
2

考虑下面的代码片段:dispatch_barrier_sync总是锁死

#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管理的读/写锁。在这个测试中,读写器都是同步的。当我使屏障异步时,测试似乎工作正常,但是我想避免异步行为,因为此实现不重要。

我想了解为什么write方法是死锁。根据GCD文档:

当阻挡块达到私人并发 队列的前部,它不立即执行。相反,队列一直等到其当前正在执行的块的 完成执行。此时, 队列自行执行障碍块。在屏障块完成之前,任何在 屏障块之后提交的块都不会执行。

我很困惑“当前正在执行的块”的含义。

我的理解是这样的场景,一群读取(X)获得提交,然后写(Y),那么更多的读取(Z):

  • (x)的执行
  • (Y)等待,直到(X)是从执行完成
  • (y)的块(Z)
  • (x)的完成
  • (Y)执行
  • (y)的完成
  • (Z)执行
  • (Z)完成
+0

我没有测试过该示例,但看了它之后,它应该_not_死锁,恕我直言。 – CouchDeveloper

回答

3

OK,经过实际测试吧:你的代码就不会阻止 - 在理论上。

但是 - 在实践中 - 它可能可能

您遇到的情况是所有可用的系统线程都耗尽的情况。为了继续,你的代码将需要GCD获得一个新的线程 - 但不可用 - 因此,它会死锁。

为了避免这种情况,您需要分析您的代码,以未绑定的方式产生新线程。这可能发生在并发队列中,其中块将阻塞或花费太长的时间完成,并且大量块以高频率被提交给该并发队列。

例如,如果你插入一个小的延迟:

for(int i = 0; i < 400; i++) { 
    usleep(1000); 
    dispatch_async(_workQueue, ^{ 
     if(arc4random() % 4 == 0) { 
      [self write]; 
     } else { 
      [self read]; 
     } 

    }); 
} 

代码可以运行,直到它完成定期。这当然只是为了证明这个问题 - 不是为了解决你的问题。

+0

这似乎是这种情况。如果我没有延迟地将循环计数减少到50,那么它运行得很好。不幸的是,无界的情况是由'MapKit'为'OverlayRenderer'请求tile导致的,我不知道如何限制它。 –

+1

@TimReddy解决方案是限制在并发队列上执行的并发块的最大数量。这听起来像,你必须使用'NSOperationQueue',但也有使用调度队列和信号量的方法。 – CouchDeveloper

+2

感谢您的提示!我现在使用'dispatch_semaphore'来限制线程数。 –