Multithreading GCD(中央大调度)中创建调度队列的数量过多?

Multithreading GCD(中央大调度)中创建调度队列的数量过多?,multithreading,grand-central-dispatch,Multithreading,Grand Central Dispatch,Mike Ash()有一篇关于Swift中构建的轻量级通知系统的精彩文章 基本思想是创建可以“侦听”的对象,即在状态发生更改时调用回调。为了保证线程安全,创建的每个对象都拥有自己的调度队列。调度队列仅用于对关键路段进行选通: dispatch_sync(self.myQueue) { // modify critical state in self } 而且它可能不会引起激烈的争论。我有点惊讶于这样一个事实:您创建的每一个可以被监听的对象都有自己的调度队列,只是为了锁定几行代码 一张海

Mike Ash()有一篇关于Swift中构建的轻量级通知系统的精彩文章

基本思想是创建可以“侦听”的对象,即在状态发生更改时调用回调。为了保证线程安全,创建的每个对象都拥有自己的调度队列。调度队列仅用于对关键路段进行选通:

dispatch_sync(self.myQueue) {
    // modify critical state in self
}
而且它可能不会引起激烈的争论。我有点惊讶于这样一个事实:您创建的每一个可以被监听的对象都有自己的调度队列,只是为了锁定几行代码

一张海报暗示OS_SPINLOCK会更快更便宜;也许吧,但它肯定会占用更少的空间

如果我的程序创建了数百个或数千个(甚至上万个)对象,我应该担心创建这么多调度队列吗?也许大多数人甚至不会被倾听,但有些人可能会

当然,两个对象不相互阻塞是有道理的,即它们有单独的锁,通常我不会再三考虑在每个对象中嵌入pthread_互斥体,而是在整个调度队列中嵌入pthread_互斥体?这真的可以吗?

对于内部工作和调度队列的确切成本是相当模糊的,但是它确实指出:

GCD提供并管理FIFO队列,应用程序可以将任务以块对象的形式提交到这些队列提交到调度队列的块在系统完全管理的线程池上执行。

因此,听起来队列不过是通过线程池对块进行排队的接口,因此在空闲时对性能没有影响/影响最小

您可以根据需要创建任意数量的串行队列

听起来,创建一个调度队列并让它闲置几乎是一个微不足道的成本

此外,我决定在一个包含一些OpenGL内容的应用程序上测试创建10000个串行和并发调度队列,但没有发现性能受到任何影响,FPS保持不变,它只使用了额外的4MB RAM(单个队列约400字节)

在使用OS_自旋锁而不是调度队列方面,苹果在其文档中非常清楚GCD比使用标准锁更有效(至少在有争议的情况下)

将基于锁的代码替换为队列可以消除与锁相关的许多惩罚,还可以简化剩下的代码。您可以创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源队列不会施加与锁相同的惩罚。例如,将任务排入队列不需要捕获内核以获取互斥锁

尽管还值得注意的是,如果您不使用队列,您始终可以释放队列,如果您担心内存问题,可以在以后需要再次使用队列时重新创建队列


TL;博士 调度队列是一种方式。您不必太担心创建大量队列而不使用它们,而且它们肯定比锁更有效


编辑:实际上,您发现自旋锁在未竞争的情况下速度更快,因此您可能希望使用它来完成此任务

我找时间做一个基准测试。我同意在有争议的案例中,自旋锁是个坏主意。在无争议的情况下,我得到的自旋锁大约需要80纳秒,而使用dispatch_sync到队列大约需要500纳秒。这是最新的双核mac mini;在我的笔记本电脑上可能快一点。对于一个队列来说,400字节显然也是相当合理的。谢谢你的意见!啊,真有趣。很高兴知道!Oops:500纳秒中的一部分也在测量形成闭包并调用它的时间。所以旋转锁仍然会更快,但这确实是因为使用旋转锁可以避免形成闭包然后调用它。如果您实际形成闭包并将其传递给锁定自旋锁、调用闭包并解锁自旋锁的函数,那么它与使用调度队列的时间几乎完全相同。(这让我想到,在常见的情况下,这可能就是调度队列正在做的事情。)根据OSSpinLockDeprecated.h头,OSSpinLock是不推荐的。而且它应该是usafe。标题说“不应再使用这些接口,特别是在不同优先级的线程可能在同一个自旋锁上竞争的情况下。”Ankit:文档中的这条评论更多地是关于如何使用调度队列,而不是创建多少调度队列。即使可以创建10000个队列,也不建议使用它们一次发送10000个块。我不认为Hamish的答案有任何误导性:就OP而言,这仍然是一个非常有效的用法。为了完整起见,应该将这些链接起来,尽管@Hamish的答案要好得多