【问题标题】:Tidwall/Safe mutex deadlockTidwall/安全互斥锁死锁
【发布时间】:2019-06-28 18:59:17
【问题描述】:

我正在使用 https://github.com/tidwall/Safe ,一个 Swift 并发库,我想我发现了一个线程错误。 (我认为我使用的是 IOS 12.3.1、iPhone Xs. Swift 4;Xcode 10.2 。)该库现在是只读的,所以我正在尝试自己调试它。不过,这个错误真的很微妙,或者它是由我什至没有想到的东西引起的,因为我做的事情与给定的库几乎相同,它工作正常,但库本身死锁了。

下面是不应该死锁的测试代码:

private func testCompetingDeadlock() {
    NSLog("start")
    let c = Chan<Int32>()
    let b = Chan<Int32>()
    let COUNT = 1000
    let wg = WaitGroup()
    wg.add(1)
    dispatch {
        NSLog("receiver starting")
        for i in 0..<(2*COUNT) {
            Thread.sleep(forTimeInterval: 0.01)
            let v = <-c
            b <- v!
        }
        wg.done()
    }
    sleep(1)
    wg.add(1)
    dispatch {
        NSLog("sender 1 starting")
        for i in 0..<COUNT {
            c <- 1
            <-b
            NSLog("1 : \(i)")
        }
        NSLog("1 done")
        wg.done()
    }
    wg.add(1)
    dispatch {
        NSLog("sender 2 starting")
        for i in 0..<COUNT {
            c <- 2
            <-b
            NSLog("2 : \(i)")
        }
        NSLog("2 done")
        wg.done()
    }
    wg.wait()
    NSLog("Both done")
}

注意send 的底层实现,又名&lt;-,是

internal func send(_ msg: T) {
    NSLog("locking (\(Thread.current)) - \(Unmanaged.passUnretained(cond.mutex).toOpaque())")
    cond.mutex.lock()
    NSLog("locked (\(Thread.current)) - \(Unmanaged.passUnretained(cond.mutex).toOpaque())")
    threadCount += 1
    defer {
        threadCount -= 1
        NSLog("unlocking (\(Thread.current)) - \(Unmanaged.passUnretained(cond.mutex).toOpaque())")
        cond.mutex.unlock()
        NSLog("unlocked (\(Thread.current)) - \(Unmanaged.passUnretained(cond.mutex).toOpaque())")
    }
    if threadCount > 1 {
        NSLog("threadCount is \(threadCount)")
    }

    if closed {
        #if os(Linux)
        assertionFailure("Send on closed channel")
        #else
        NSException.raise(NSExceptionName(rawValue: "Exception"), format: "send on closed channel", arguments: getVaList([]))
        #endif
    }
    msgs.append(msg)
    broadcast()
    while msgs.count > cap {
        cond.wait()
    }
}

(我添加了日志记录,threadCount。一旦发生死锁,threadCount 为 2。我在 Mutex 类中尝试了相同的“锁定后 inc,解锁前 dec”,我得到 3 在死锁期间??? 我不知道是怎么回事,也没有进一步调查,尽管它可能是一个重要的线索。)

如果运行testCompetingDeadlock,死锁通常会立即发生,两个发送线程卡在sendcond.wait() 行,都在同一个互斥锁的锁定区域内。我不知道怎么做。我尝试测试Mutex 本身,就像我认为send 使用它一样,如下:

private func testSafeMutex() {
    let mutex = Mutex()

    dispatch {
        NSLog("1 locking")
        mutex.lock()
        NSLog("1 locked")
        defer {
            NSLog("1 unlocking")
            mutex.unlock()
            NSLog("1 unlocked")
        }

        sleep(1)
    }

    dispatch {
        NSLog("2 locking")
        mutex.lock()
        NSLog("2 locked")
        defer {
            NSLog("2 unlocking")
            mutex.unlock()
            NSLog("2 unlocked")
        }

        sleep(1)
    }
}

但是,工作正常 - 没有死锁。

我真的不知道该怎么做,只是添加越来越细的日志记录,并尝试合并两个测试用例直到找到关键的差异(这很困难,因为很难保持代码的功能版本之间)。有人可以帮我调试这个库吗?是否可能存在一些特定于 iOS 的内存模型问题等?

【问题讨论】:

    标签: ios swift concurrency open-source


    【解决方案1】:

    我最终弄明白了——我忘记了最后的cond.wait() 释放了锁,这解释了为什么我在一个应该被锁定的部分中有多个线程。弄清楚这一点后,我找出了真正的问题并提出了解决方案:https://github.com/Erhannis/Safe/commit/cfa41231d01895457bfc1421d779a29a18923c5b

    据我了解,真正的问题基本上是退出while 循环的条件是错误的——所有发送线程都会等到缓冲区有空间,但那些消息已被读取的线程应该已经退出阅读他们的消息后循环。

    【讨论】:

      猜你喜欢
      • 2015-10-26
      • 1970-01-01
      • 1970-01-01
      • 2011-03-03
      • 2012-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多