dispatch_sync对@synchronized具有哪些优点?

可以说我想让这个代码是线程安全的:

- (void) addThing:(id)thing { // Can be called from different threads [_myArray addObject:thing]; } 

GCD接缝就像实现这一点的首选方式:

 - (void) addThing:(id)thing { dispatch_sync(_myQueue, ^{ // _myQueue is serial. [_myArray addObject:thing]; }); } 

与传统方法相比,它有什么优势?

 - (void) addThing:(id)thing { @synchronized(_myArray) { [_myArray addObject:thing]; } } 

哇。 好的 – 我原来的performance评估是错误的。 让我着迷。

不那么愚蠢。 我的performancetesting是错误的。 固定。 随着深入的GCD代码。

更新:基准代码可以在这里find: https : //github.com/bbum/StackOverflow希望,现在是正确的。 🙂

Update2:添加了每种testing的10个队列版本。

好。 重写答案:

@synchronized()已经存在很长时间了。 它被实现为一个哈希查找find一个锁,然后被locking。 这是“相当快” – 通常速度不够快,但可能是高争用的负担(任何同步原语)。

dispatch_sync()不一定需要locking,也不需要复制块。 具体来说,在快速path的情况下, dispatch_sync()将直接在调用线程上调用块而不复制块。 即使在slowpath的情况下,块也不会被复制,因为调用线程必须阻塞直到执行(调用线程被挂起,直到dispatch_sync()完成之前的任何工作,然后线程被恢复)。 一个例外是主队列/线程上的调用; 在这种情况下,块仍然不被复制(因为调用线程被挂起,因此使用堆栈中的块是可以的),但是有一大堆工作要在主队列中排队,执行和然后恢复调用线程。

dispatch_async()要求复制该块,因为它不能在当前线程上执行, 不能阻塞当前线程(因为该块可能会立即locking某些线程本地资源,该线程本地资源仅在dispatch_async()之后的代码行中可用dispatch_async()虽然很贵, dispatch_async()将工作从当前线程移出,使其立即恢复执行。

最终结果 – dispatch_sync()@synchronized快,但不是一个有意义的数量(在'12 iMac上,也不是'mac mini – 两者之间的差异是非常不同的,btw …并发的乐趣)。 使用dispatch_async()比无约束情况下的要慢,但不是太多。 但是,当资源处于争用状态时,使用“dispatch_async()”的速度会明显加快。

 @synchronized uncontended add: 0.14305 seconds Dispatch sync uncontended add: 0.09004 seconds Dispatch async uncontended add: 0.32859 seconds Dispatch async uncontended add completion: 0.40837 seconds Synchronized, 2 queue: 2.81083 seconds Dispatch sync, 2 queue: 2.50734 seconds Dispatch async, 2 queue: 0.20075 seconds Dispatch async 2 queue add completion: 0.37383 seconds Synchronized, 10 queue: 3.67834 seconds Dispatch sync, 10 queue: 3.66290 seconds Dispatch async, 2 queue: 0.19761 seconds Dispatch async 10 queue add completion: 0.42905 seconds 

拿一点盐, 它是最糟糕的微观基准,它不代表任何真实的世界常见的使用模式。 “工作单位”如下,上述执行次数为100万次执行。

 - (void) synchronizedAdd:(NSObject*)anObject { @synchronized(self) { [_a addObject:anObject]; [_a removeLastObject]; _c++; } } - (void) dispatchSyncAdd:(NSObject*)anObject { dispatch_sync(_q, ^{ [_a addObject:anObject]; [_a removeLastObject]; _c++; }); } - (void) dispatchASyncAdd:(NSObject*)anObject { dispatch_async(_q, ^{ [_a addObject:anObject]; [_a removeLastObject]; _c++; }); } 

(_c在每次开始时被重置为0,并被声明为最后的#个testing用例,以确保代码在执行所有工作之前执行所有工作)。

对于无争议的情况:

 start = [NSDate timeIntervalSinceReferenceDate]; _c = 0; for(int i = 0; i < TESTCASES; i++ ) { [self synchronizedAdd:o]; } end = [NSDate timeIntervalSinceReferenceDate]; assert(_c == TESTCASES); NSLog(@"@synchronized uncontended add: %2.5f seconds", end - start); 

对于争用的2队列情况(q1和q2是连续的):

  #define TESTCASE_SPLIT_IN_2 (TESTCASES/2) start = [NSDate timeIntervalSinceReferenceDate]; _c = 0; dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_apply(TESTCASE_SPLIT_IN_2, serial1, ^(size_t i){ [self synchronizedAdd:o]; }); }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_apply(TESTCASE_SPLIT_IN_2, serial2, ^(size_t i){ [self synchronizedAdd:o]; }); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); end = [NSDate timeIntervalSinceReferenceDate]; assert(_c == TESTCASES); NSLog(@"Synchronized, 2 queue: %2.5f seconds", end - start); 

以上只是重复每个工作单位的变种(没有tricksy运行时 – y魔术在使用; copypasta FTW!)。


考虑到这一点:

•使用@synchronized()如果你喜欢它的外观。 事实是,如果你的代码在这个arrays上竞争,你可能会遇到一个架构问题。 注意 :使用@synchronized(someObject)可能会有意想不到的后果,如果对象内部使用@synchronized(self) ,则可能会导致额外的争用!

•使用dispatch_sync()与串行队列,如果这是你的事情。 没有开销 – 在竞争和无竞争的情况下实际上速度更快 – 使用队列更容易debugging,更容易进行configuration。仪器和debugging器都具有优秀的debugging队列工具(而且它们越来越好所有的时间),而debugging锁可能是一个痛苦。

•使用dispatch_async()和非常有争议的资源的不可变数据。 即:

 - (void) addThing:(NSString*)thing { thing = [thing copy]; dispatch_async(_myQueue, ^{ [_myArray addObject:thing]; }); } 

最后, 维护一个数组的内容应该用哪一个 。 同步案例的争用成本非常高。 对于asynchronous情况来说,争用的成本会降低, 复杂性或怪异性能问题的可能性会随之上升。

在devise并发系统时,最好尽量保持队列之间的边界。 其中很大一部分是确保尽可能less的资源在边界两侧“生存”。

好吧,我做了更多的testing,结果如下:

lockingtesting:平均值:2.48661,stdDev:0.50599

同步testing:平均值:2.51298,stdDev:0.49814

调度testing:平均值:2.17046,stdDev:0.43199

所以我错了,我的坏(如果有人对testing代码感兴趣,这是在这里:

 static NSInteger retCount = 0; @interface testObj : NSObject @end @implementation testObj -(id)retain{ retCount++; return [super retain]; } @end @interface ViewController : UIViewController{ NSMutableArray* _a; NSInteger _c; NSLock* lock; NSLock* thlock; dispatch_queue_t _q; } - (IBAction)testBtn:(id)sender; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } -(NSTimeInterval)testCase:(SEL)aSel name:(NSString*)name{ _a = [[NSMutableArray alloc] init]; retCount = 0; //Sync test NSThread* th[10]; for(int t = 0; t < 10;t ++){ th[t] = [[NSThread alloc] initWithTarget:self selector:aSel object:nil]; } NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate]; for(int t = 0; t < 10;t ++){ [th[t] start]; } NSInteger thCount = 1; while(thCount > 0){ thCount = 0; for(int t = 0; t < 10;t ++){ thCount += [th[t] isFinished] ? 0 : 1; } } NSTimeInterval end = [NSDate timeIntervalSinceReferenceDate]; NSLog(@"%@: %2.5f, retainCount:%d, _c:%d, objects:%d", name, end-start, retCount, _c, [_a count]); [_a release]; for(int t = 0; t < 10;t ++){ [th[t] release]; } return end-start; } -(void)syncTest{ for(int t = 0; t < 5000; t ++){ [self synchronizedAdd:[[[testObj alloc] init] autorelease] ]; } } -(void)dispTest{ for(int t = 0; t < 5000; t ++){ [self dispatchSyncAdd:[[[testObj alloc] init] autorelease] ]; } } -(void)lockTest{ for(int t = 0; t < 5000; t ++){ [self lockAdd:[[[testObj alloc] init] autorelease] ]; } } - (void) synchronizedAdd:(NSObject*)anObject { @synchronized(self) { [_a addObject:anObject]; _c++; } } - (void) dispatchSyncAdd:(NSObject*)anObject { dispatch_sync(_q, ^{ [_a addObject:anObject]; _c++; }); } - (void) lockAdd:(NSObject*)anObject { [lock lock]; [_a addObject:anObject]; _c++; [lock unlock]; } - (double)meanOf:(NSArray *)array { double runningTotal = 0.0; for(NSNumber *number in array) { runningTotal += [number doubleValue]; } return (runningTotal / [array count]); } - (double)standardDeviationOf:(NSArray *)array { if(![array count]) return 0; double mean = [self meanOf:array]; double sumOfSquaredDifferences = 0.0; for(NSNumber *number in array) { double valueOfNumber = [number doubleValue]; double difference = valueOfNumber - mean; sumOfSquaredDifferences += difference * difference; } return sqrt(sumOfSquaredDifferences / [array count]); } -(void)stats:(NSArray*)data name:(NSString*)name{ NSLog(@"%@: mean:%2.5f, stdDev:%2.5f", name, [self meanOf:data], [self standardDeviationOf:data]); } - (IBAction)testBtn:(id)sender { _q = dispatch_queue_create("array q", DISPATCH_QUEUE_SERIAL); lock = [[NSLock alloc] init]; NSMutableArray* ltd = [NSMutableArray array]; NSMutableArray* std = [NSMutableArray array]; NSMutableArray* dtd = [NSMutableArray array]; for(int t = 0; t < 20; t++){ [ltd addObject: @( [self testCase:@selector(lockTest) name:@"lock Test"] )]; [std addObject: @( [self testCase:@selector(syncTest) name:@"synchronized Test"] )]; [dtd addObject: @( [self testCase:@selector(dispTest) name:@"dispatch Test"] )]; } [self stats: ltd name:@"lock test"]; [self stats: std name:@"synchronized test"]; [self stats: dtd name:@"dispatch Test"]; } @end 

我发现dispatch_sync()是一个不好的方法来locking,它不支持嵌套的调用。

所以你不能在一个串行Q上调用dispatch_sync,然后在一个具有相同Q的子例程中再次调用它。这意味着它不像@synchronized那样工作。

有几件事情:1)@Synchronize是一些监视器上的encryption版本(我个人更喜欢NSLock / NSRecursiveLock)2)Dispatch_sync是build立执行队列。

这两种方法导致类似的结果在你的情况下,但如此简单的解决scheme,使收集线程安全我宁愿1。

为什么:

  • 如果你有多个内核,那么多个线程可能同时工作。 根据调度程序的不同,他们会在显示器上locking很短的时间。

  • 它比分配新块要轻得多,保留“放入队列”(这也是线程同步),并在工作队列准备就绪时执行。

  • 在这两种方式的执行顺序将会有很大的不同。

  • 如果在某个时候发现大量使用集合,您可以考虑将读/写types更改locking,如果您使用一些类似NSLock的类而不是sync_queue,则重构/更改会更简单。