Rob*_*ert 28 multithreading objective-c thread-safety ios
让我们说我想让这段代码线程安全:
- (void) addThing:(id)thing { // Can be called from different threads
[_myArray addObject:thing];
}
Run Code Online (Sandbox Code Playgroud)
GCD似乎是实现这一目标的首选方式:
- (void) addThing:(id)thing {
dispatch_sync(_myQueue, ^{ // _myQueue is serial.
[_myArray addObject:thing];
});
}
Run Code Online (Sandbox Code Playgroud)
与传统方法相比,它有什么优势?
- (void) addThing:(id)thing {
@synchronized(_myArray) {
[_myArray addObject:thing];
}
}
Run Code Online (Sandbox Code Playgroud)
bbu*_*bum 49
哇.好的 - 我原来的绩效评估是错误的.让我变得愚蠢.
不是那么愚蠢.我的表现测试错了.固定.随着深入研究GCD代码.
更新:代码为基准可以在这里找到:https://github.com/bbum/StackOverflow 希望,现在是正确的.:)
Update2:添加了每种测试的10个队列版本.
好.重写答案:
• @synchronized()已经存在了很长时间.它被实现为哈希查找以查找随后被锁定的锁.它"非常快" - 通常足够快 - 但在高争用下可能是一种负担(任何同步原语都可以).
• dispatch_sync()不一定需要锁定,也不需要复制块.具体来说,在快速路径的情况下,dispatch_sync()将直接在调用线程上调用块而不复制块.即使在慢路径情况下,也不会复制该块,因为调用线程必须阻塞直到执行(调用线程被挂起,直到dispatch_sync()完成之前的任何工作,然后线程被恢复).一个例外是主队列/线程上的调用; 在这种情况下,块仍然没有被复制(因为调用线程被挂起,因此,使用堆栈中的块是正常的),但是有很多工作要在主队列上排队,执行,和然后恢复调用线程.
• dispatch_async()要求复制块,因为它无法在当前线程上执行,也不能阻止当前线程(因为块可能会立即锁定某些线程本地资源,该资源仅在代码行之后可用dispatch_async().虽然昂贵,dispatch_async()将工作从当前线程移开,允许它立即恢复执行.
最终结果 - dispatch_sync()速度快于@synchronized,但不是通常有意义的数量(在'12 iMac上,也不是'11 mac mini - 两者之间的#s非常不同,顺便说一句......并发的乐趣).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
Run Code Online (Sandbox Code Playgroud)
带上一粒盐; 它是最糟糕的微基准,因为它不代表任何现实世界的常见使用模式."工作单位"如下,上述执行时间代表1,000,000次执行.
- (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++;
});
}
Run Code Online (Sandbox Code Playgroud)
(_c在每次传递开始时重置为0,并在结束时声明为==到测试用例数,以确保代码在喷出时间之前实际执行所有工作.)
对于无竞争的情况:
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);
Run Code Online (Sandbox Code Playgroud)
对于竞争,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);
Run Code Online (Sandbox Code Playgroud)
以上简单地针对每个工作单元变体重复(没有特技运行时使用魔法; copypasta FTW!).
考虑到这一点:
• @synchronized()如果您喜欢它的外观,请使用.实际情况是,如果您的代码在该阵列上竞争,您可能会遇到架构问题. 注意:使用@synchronized(someObject)可能会产生意想不到的后果,如果对象内部使用它可能会导致额外的争用@synchronized(self)!
• dispatch_sync()如果您喜欢,请使用串行队列.没有任何开销 - 在竞争和非竞争情况下实际上都更快 - 并且使用队列更容易调试并且更容易在该工具中进行分析,并且调试器都具有用于调试队列的优秀工具(并且它们正在变得更好所有的时间)而调试锁可能是一个痛苦.
• dispatch_async()与不可变数据一起使用以获得大量竞争资源.即:
- (void) addThing:(NSString*)thing {
thing = [thing copy];
dispatch_async(_myQueue, ^{
[_myArray addObject:thing];
});
}
Run Code Online (Sandbox Code Playgroud)
最后,用于维护数组内容的哪一个并不重要.同步案例的争用成本非常高.对于异步情况,争用成本会下降,但复杂性或奇怪性能问题的可能性会上升.
在设计并发系统时,最好保持队列之间的边界尽可能小.其中很大一部分是确保尽可能少的资源"生活"在边界的两边.
| 归档时间: |
|
| 查看次数: |
8816 次 |
| 最近记录: |