本文介绍了在取消初始化之前发出信号量信号安全吗?以防万一?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

class SomeViewController: UIViewController {
    let semaphore = DispatchSemaphore(value: 1)

    deinit {
        semaphore.signal() // just in case?
    }

    func someLongAsyncTask() {
        semaphore.wait()
        ...
        semaphore.signal() // called much later
    }
}
如果我告诉信号量等待,然后在信号量被告知发出信号之前取消初始化拥有它的视图控制器,应用程序崩溃并出现Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)错误。但是,如果我只是在视图控制器的deinit方法中调用semaphore.signal(),灾难就避免了。然而,如果异步函数在deinit被调用之前返回,并且视图控制器被取消初始化,则signal()被调用两次,这似乎不是问题。但这样做安全和/或明智吗?

推荐答案

您在DispatchSemaphore中遇到了一个功能/错误。如果查看堆栈跟踪并跳到堆栈的顶部,您将看到带有消息的程序集:

例如,

这是因为DispatchSemaphore检查与信号量相关联的value是否小于deinit,如果是,则失败。简而言之,如果该值较小,则libDispatch得出结论,该信号量仍在使用中。

这可能看起来过于保守,因为这通常发生在客户端草率的情况下,而不是因为一定有一些严重的问题。如果它发出一个有意义的异常消息,而不是强迫我们挖掘堆栈跟踪,那就更好了。但这就是libDispatch的工作方式,我们必须接受它。

话虽如此,有三种可能的解决方案:

  1. 您显然有一条执行路径,在对象被释放之前,您正在执行而不是到达signal。更改代码,这样就不会发生这种情况,您的问题也就消失了。

  2. 虽然您只需确保waitsignal呼叫是均衡的(修复问题的根源),但您可以在您的问题中使用该方法(解决问题的症状)。但deinit方法通过使用非局部推理解决了问题。如果您更改了初始化,例如,值为5,则您或某个未来的程序员必须记住也要转到deinit并再插入四个signal调用。

    另一种方法是使用零值实例化信号量,然后在初始化期间,只需signal足够的次数就可以使该值达到您想要的位置。那么你就不会有这个问题了。这将使问题的解决局限于初始化,而不是每次在初始化期间更改非零值时都必须记住调整deinit

    请参阅https://lists.apple.com/archives/cocoa-dev/2014/Apr/msg00483.html

  3. ITAI列举了一些根本不应该使用信号量的原因。还有很多其他原因:

    • 信号量与新的SWIFT并发系统不兼容(请参阅Swift concurrency: Behind the scenes);
    • 信号量如果在代码中不精确,也很容易导致死锁;
    • 信号量通常与可取消的异步例程等相反。

    如今,信号量几乎总是错误的解决方案。如果您告诉我们您正在尝试使用信号量解决什么问题,我们或许能够推荐其他更好的解决方案。


您说:

从技术上讲,过度发送信号不会带来新的问题,因此您真的不必担心这一点。但这种"以防万一"的过度信号确实有一丝代码味道。它告诉您,在某些情况下,您正在等待但从未到达信令,这表明存在逻辑错误(请参阅上面的第1点)。

这篇关于在取消初始化之前发出信号量信号安全吗?以防万一?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

11-03 12:32