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,则重构/更改会更简单。