【问题标题】:Should semaphore wait and signal always be called from separate queues?是否应该始终从单独的队列中调用信号量等待和信号?
【发布时间】:2020-05-27 21:00:50
【问题描述】:

我正在检查使用 GCD 的信号量的正确实现细节,当来自 (https://khanlou.com/2016/04/the-GCD-handbook/) 的一条语句让我感到困惑:“调用 .wait() 将阻塞线程,直到调用 .signal()。这意味着 .signal () 必须从不同的线程调用,因为当前线程完全被阻塞。此外,你不应该从主线程调用 .wait(),只能从后台线程调用。 大多数信号量示例通常从同一个队列调用等待和信号,这似乎也可以正常工作。我在这里遗漏了什么吗?

// Pseudocode from: https://khanlou.com/2016/04/the-GCD-handbook/
// on a background queue
let semaphore = DispatchSemaphore(value: 0)
doSomeExpensiveWorkAsynchronously(completionBlock: {
    semaphore.signal()
})
semaphore.wait()
//the expensive asynchronous work is now done

【问题讨论】:

  • 大概完成块不会在同一个队列上运行,否则这根本行不通。此外,等待异步工作使其同步。如果你在等待它,它不是异步的。这首先破坏了它异步的全部目的。
  • 还有一点值得注意,线程和队列是不同的东西。队列将工作分派到可用线程上,但除了主队列/主线程的情况外,队列和特定线程之间没有绑定。
  • 如果您想强制异步任务变为同步,请不要这样做。学习了解异步数据处理的工作原理。
  • 基本上,如果您完全使用信号量,那么您做错了。最好考虑现实生活和实际任务,而不是仅仅为了四处碰碰而碰碰边缘情况。
  • 不要搞错队列线程。如果一个队列是并发队列,它可以在多个线程上执行工作项。如果你在一个线程上调用.wait,并期待未来来自同一个线程的.signal 调用,你会很难过(也就是说,永远被阻塞)。另外,不要打电话给.wait。使用完成块等。调度组对于这种情况可能是一个有用的工具。

标签: ios swift multithreading grand-central-dispatch dispatchsemaphore


【解决方案1】:

你问:

是否应该始终从不同的队列调用信号量等待和信号?

信号量总是从单独的线程中调用。这就是信号量的目的,一个线程发送一个信号,另一个线程将等待。这意味着从同一个并发队列调用信号量是安全的(因为单独调度的任务在不同的工作线程上运行),但从同一个串行队列调用信号量是不安全的。显然,从不同队列调用信号量也是安全的。要点是它必须是不同的线程。

您分享了该文档的引用,作者所说的一切都是绝对正确的。等待和信号调用必须从不同的线程完成。而且我们永远不想在主线程上等待某个信号由其他耗时的进程发送。

你接着说:

信号量的大多数示例通常从同一个队列调用等待和信号,这似乎也可以正常工作。我在这里遗漏了什么吗?

// Pseudocode from: https://khanlou.com/2016/04/the-GCD-handbook/
// on a background queue
let semaphore = DispatchSemaphore(value: 0)
doSomeExpensiveWorkAsynchronously(completionBlock: {
    semaphore.signal()
})
semaphore.wait()
//the expensive asynchronous work is now done

一些观察:

  1. 此模式仅在 signalwait 位于不同线程上时有效。如果它们是同一个线程,这将死锁。很明显,作者假设他们在不同的线程上。

  2. 您似乎在暗示这两个呼叫“在同一个队列中”。这不是一个有效的假设(坦率地说,这不太可能)。我们需要看到“昂贵的异步”方法的实现才能确定。但是当你看到这样的闭包时,通常意味着该方法将这个闭包分派到它自己选择的某个 GCD 队列中。而且我们无法知道它使用的是哪个。 (您必须查看它的实现才能确定。)但它不太可能是同一个队列。这段代码假定它必须是一个不同的线程。

  3. 您在此处与我们分享的整个模式都是不明智的。它实际上采用了异步方法,使用信号量使其行为同步,但代码注释表明整个事情被分派到后台队列(以避免阻塞主线程),从而使其再次异步。这有点折磨人。你真的应该继续从主线程调用这个昂贵/异步的方法(这是安全的,因为它异步运行)并完全失去信号量。也许作者在扭曲自己以说明如何使用信号量,但这是一个可怕的例子。

【讨论】:

  • 我认为上述文章中使用的示例似乎引发了很多无意的问题。 @Rob 感谢您的详细解释。我实际上误解了带有队列的线程我想根据您的解释记下几点:1.信号量本身管理单独的线程来调用等待和信号,IFF调用并发队列。 2. 串行队列在单个线程上分派所有工作。我仍然想了解使用信号量在多个异步任务之间添加依赖关系是一种好习惯还是有更好的方法?
  • “使用信号量在多个异步任务之间添加依赖关系是一种好习惯吗?” - 因为信号量在误用时很容易出现死锁,而且因为调用wait 会阻塞线程(这是相当有限的),所以信号量通常是我们最后使用的工具。根据应用程序,调度组(使用notify,而不是wait)通常更可取。或者使用自定义Operation 子类。或者,现在,使用 Combine。但我们不能抽象地回答这个问题。你必须提供具体的例子。但是您在问题中给出的示例是信号量使用不当的教科书示例。
  • @coderex - 1. 是的,信号量用于不同线程之间的通信,无论这些线程来自完全不同的队列还是同一并发队列使用的不同工作线程(但不适用于串行队列)。 2. 串行队列一次只在一个线程上调度它们的所有工作。诚然,在自定义串行队列上,从一个调用到另一个调用可能是不同的工作人员,但由于在给定时间可能只有一个工作人员可以运行,所以关于不使用信号量让串行队列等待来自自身的信号的警告仍然存在立场。
猜你喜欢
  • 2014-08-03
  • 2012-10-18
  • 1970-01-01
  • 1970-01-01
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-15
相关资源
最近更新 更多