了解 dispatch_queues 和同步/异步调度

Posted

技术标签:

【中文标题】了解 dispatch_queues 和同步/异步调度【英文标题】:Understanding dispatch_queues and synchronous/asynchronous dispatch 【发布时间】:2017-02-03 23:22:42 【问题描述】:

我是一名 android 工程师,试图移植一些使用 5 个串行调度队列的 ios 代码。我想确保我以正确的方式思考问题。

dispatch_sync 到 SERIAL 队列基本上是将队列用作同步队列 - 只有一个线程可以访问它,并且可以将执行的块视为关键区域。它立即发生在当前线程上——相当于

get_semaphore()
queue.pop()
do_block()
release_semaphore()

dispatch_async 到串行队列 - 在另一个线程上执行块并让当前线程立即返回。然而,由于它是一个串行队列,它保证一次只会执行其中一个异步线程(对 dispatch_async 的下一次调用将等到所有其他线程完成)。该块也可以被认为是一个关键区域,但它会发生在另一个线程上。所以与上面的代码相同,但它首先传递给工作线程。

我是不是搞错了,还是我猜对了?

【问题讨论】:

【参考方案1】:

这感觉像是一种过于复杂的思考方式,并且该描述中有很多不完全正确的小细节。具体来说,“它立即发生在当前线程上”是不正确的。

首先,让我们退后一步:dispatch_asyncdispatch_sync 之间的区别仅仅是当前线程是否等待它。但是,当您将某些内容分派到串行队列时,您应该始终想象它运行在 GCD 自己选择的单独工作线程上。是的,作为一种优化,有时dispatch_sync 会使用当前线程,但你无法保证这一事实。

其次,当您讨论dispatch_sync 时,您会说它“立即”运行。但这绝不能保证立即生效。如果一个线程对某个串行队列执行dispatch_sync,则该线程将阻塞,直到(a)当前在该串行队列上运行的任何块完成; (b) 该串行队列运行和完成的任何其他排队块; (c) 显然,线程 A 本身分派的块运行并完成。

现在,当您使用串行队列进行同步时,一些线程安全地访问内存中的某个对象,通常该同步过程非常快,因此等待线程通常会被阻塞一段可忽略不计的时间,因为已调度的块(以及任何先前已调度的块)来完成。但总的来说,说它会立即运行是一种误导。 (如果它总是可以立即运行,那么您就不需要队列来同步访问)。


现在您的问题涉及“关键区域”,我假设您正在谈论一些代码,为了确保线程安全或出于其他类似原因,必须同步这些代码。因此,当运行此代码以进行同步时,dispatch_syncdispatch_async 的唯一问题是当前线程是否必须等待。例如,一种常见的模式是说一个可能dispatch_async 写入某个模型(因为在继续之前无需等待模型更新),但dispatch_sync 从某个模型读取(因为你显然不'在返回读取值之前不想继续)。

该同步/异步模式的进一步优化是读写器模式,其中允许并发读取但不允许并发写入。因此,您将使用并发队列,dispatch_barrier_async 写入(实现类似串行的写入行为),但 dispatch_sync 读取(相对于其他读取操作享受并发性能)。

【讨论】:

临界区是临界区的别称:en.wikipedia.org/wiki/Critical_section 是的,我以为你就是这个意思。在 iOS 中,我们将其称为需要“同步”。见Threading Programming Guide: Synchronization。但这早于 GCD,在 Concurrency Programming Guide: Eliminating Lock-Based Code 中进行了讨论。 是的。所以我有点草率地把事情说成是即时的,但我知道它做对了。幸运的是,我认为我可以将 Android 实现简化为 1 个消息队列,没有围绕核心数据通知构建的架构会使事情变得更容易。【参考方案2】:

为了挑剔,dispatch_sync 不一定在当前线程上运行代码,但如果没有,它仍然会阻塞当前线程,直到任务完成。仅当您依赖线程 ID 或线程本地存储时,这种区别才可能很重要。

但除此之外,是的,除非我错过了一些微妙的东西。

【讨论】:

很高兴知道。修改后的答案。

以上是关于了解 dispatch_queues 和同步/异步调度的主要内容,如果未能解决你的问题,请参考以下文章

110 同步异步阻塞非阻塞

同步异步阻塞和非阻塞

Node.js 同步与异步

转:关于阻塞非阻塞同步与异步的了解

同步异步阻塞非阻塞

关于ajax