dispatch_sync比@synchronized有什么优势? [英] What advantage(s) does dispatch_sync have over @synchronized?

查看:101
本文介绍了dispatch_sync比@synchronized有什么优势?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我说我想让这段代码线程安全:

Lets say I want to make this code thread-safe:

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

GCD接缝是实现此目标的首选方式:

GCD seams like the preferred way of achieving this:

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

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

What advantage(s) does it have over the traditional method?

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


推荐答案

<罢工>哇。好的 - 我原来的绩效评估是错误的。让我变得愚蠢。

不是那么愚蠢。我的表现测试错了。固定。随着深入研究GCD代码。

Not so stupid. My performance test was wrong. Fixed. Along with a deep dive into the GCD code.

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

Update: Code for the benchmark can be found here: https://github.com/bbum/StackOverflow Hopefully, it is correct now. :)

Update2:为每种测试添加了10个队列版本。

Update2: Added a 10 queue version of each kind of test.

确定。重写答案:

@synchronized()已存在很长时间了。它被实现为哈希查找以查找随后被锁定的锁。它非常快 - 通常足够快 - 但在高争用下可能是一种负担(任何同步原语也是如此)。

• @synchronized() has been around for a long time. It is implemented as a hash lookup to find a lock that is then locked. It is "pretty fast" -- generally fast enough -- but can be a burden under high contention (as can any synchronization primitive).

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

dispatch_sync() doesn't necessarily require a lock, nor does it require the block to be copied. Specifically, in the fastpath case, the dispatch_sync() will call the block directly on the calling thread without copying the block. Even in the slowpath case, the block won't be copied as the calling thread has to block until execution anyway (the calling thread is suspended until whatever work is ahead of the dispatch_sync() is finished, then the thread is resumed). The one exception is invocation on the main queue/thread; in that case, the block still isn't copied (because the calling thread is suspended and, therefore, using a block from the stack is OK), but there is a bunch of work done to enqueue on the main queue, execute, and then resume the calling thread.

dispatch_async()要求复制块,因为不能执行当前线程可以阻止当前线程(因为该块可能会立即锁定某些线程本地资源,该资源仅在<$ c之后的代码行上可用$ c> dispatch_async()。虽然价格昂贵,但 dispatch_async()将工作移出当前线程,允许它立即恢复执行。

• dispatch_async() required that the block be copied as it cannot execute on the current thread nor can the current thread be blocked (because the block may immediately lock on some thread local resource that is only made available on the line of code after the dispatch_async(). While expensive, dispatch_async() moves the work off the current thread, allowing it to resume execution immediately.

最终结果 - dispatch_sync()快于 @synchronized ,但不是通常有意义的数量(在'12 iMac上,也不是'11 mac mini - 两者之间的#s非常不同,顺便说一句......并发的乐趣)。使用 dispatc在非竞争情况下,h_async()比两者都慢,但不是很多。但是,当资源处于争用状态时,使用'dispatch_async()'会明显加快。

End result -- dispatch_sync() is faster than @synchronized, but not by a generally meaningful amount (on a '12 iMac, nor '11 mac mini -- #s between the two are very different, btw... joys of concurrency). Using dispatch_async() is slower than both in the uncontended case, but not by much. However, use of 'dispatch_async()' is significantly faster when the resource is under contention.

@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

带上一粒盐;它是最差类型的微基准,因为它不代表任何现实世界的常见使用模式。 工作单位如下,上述执行时间代表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++;
    });
}

(_ c在每次传递开始时重置为0并声明为==到最后的测试用例数,以确保代码在喷出时间之前实际执行所有工作。)

(_c is reset to 0 at the beginning of each pass and asserted to be == to the # of test cases at the end to ensure that the code is actually executing all the work before spewing the time.)

对于无竞争情况:

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是串行的):

For the contended, 2 queue, case (q1 and q2 are serial):

    #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);

以上只是为每个工作单元变体重复上述(没有特技运行时使用魔法; copypasta FTW!)。

The above are simply repeated for each work unit variant (no tricksy runtime-y magic in use; copypasta FTW!).

考虑到这一点:

•如果您喜欢它的外观,请使用 @synchronized()。实际情况是,如果您的代码在该阵列上竞争,您可能会遇到架构问题。 注意:使用 @synchronized(someObject)可能会产生意想不到的后果,如果对象内部使用,它可能会导致额外的争用@synchronized(self)

• Use @synchronized() if you like how it looks. The reality is that if your code is contending on that array, you probably have an architecture issue. Note: using @synchronized(someObject) may have unintended consequences in that it may cause additional contention if the object internally uses @synchronized(self)!

•使用 dispatch_sync()和一个串行队列如果那是你的事。没有任何开销 - 在竞争和非竞争情况下实际上都更快 - 并且使用队列更容易调试并且更容易在该工具中进行分析,并且调试器都具有用于调试队列的优秀工具(并且它们正在变得更好所有的时间)而调试锁可能会很痛苦。

• Use dispatch_sync() with a serial queue if that is your thing. There is no overhead -- it is actually faster in both the contended and uncontended case -- and using queues are both easier to debug and easier to profile in that Instruments and the Debugger both have excellent tools for debugging queues (and they are getting better all the time) whereas debugging locks can be a pain.

•使用 dispatch_async()使用不可变数据争用资源。即:

• Use dispatch_async() with immutable data for heavily contended resources. I.e.:

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






最后,使用哪一个来维护数组的内容并不重要。同步案例的争用成本非常高。对于异步情况,争用成本会下降,可能会出现复杂性或奇怪的性能问题。


Finally, it shouldn't really matter which one you use for maintaining the contents of an array. The cost of contention is exceedingly high for the synchronous cases. For the asynchronous case, the cost of contention goes way down, but the potential for complexity or weird performance issues goes way up.

设计时并发系统,最好保持队列之间的边界尽可能小。其中很大一部分是确保尽可能少的资源生活在边界的两边。

When designing concurrent systems, it is best to keep the boundary between queues as small as possible. A big part of that is ensuring that as few resources as possible "live" on both sides of a boundary.

这篇关于dispatch_sync比@synchronized有什么优势?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆