Tidwall/Safe Swift并发库互斥锁死锁问题调试求助
Safe Library's Chan on iOS 12.3.1 (Swift 4) Let's break down what's going on here and how to resolve it. From your debugging logs and test cases, the problem isn't with the mutex itself—it's how the condition variable (used for backpressure in the Chan class) interacts with the mutex, especially on older iOS versions like 12.3.1.
Key Clues from Your Debugging
- You're seeing two sender threads stuck in
cond.wait()while both holding the same mutex lock (evidenced bythreadCounthitting 2). That's impossible with a properly working mutex—so the condition variable'swait()method isn't correctly releasing the mutex when waiting. - Your standalone
Mutextest works fine, which confirms the mutex implementation itself is solid. The issue is isolated to howChancombines the mutex with condition variables for backpressure.
Why This Is Happening
The core problem is almost certainly a flaw in how the Safe library's condition variable handles the mutex during wait(). Here's the breakdown:
- Atomic Unlock/Wait/Lock Requirement: POSIX condition variables (like
pthread_cond_wait()) are designed to atomically release the mutex when waiting, then re-acquire it when signaled. If the library'scond.wait()method doesn't do this—maybe it's a custom implementation that skips the atomic unlock—multiple threads can end up holding the same mutex at once, leading to deadlock. - Backpressure Loop + Spurious Wakeups: Even if the wait works correctly, your
send()loop useswhile msgs.count > cap(which is good, since spurious wakeups are allowed), but if the receiver doesn't send abroadcast()every time it consumes a message, waiting threads will never wake up. - iOS 12 Interoperability Quirks: Older iOS versions had subtle bugs when Swift code interacts with pthread primitives. If the
Safelibrary's condition variable doesn't account for these, it could leave the mutex in an inconsistent state afterwait().
Steps to Diagnose & Fix
1. Check the Condition Variable's wait() Implementation
First, look at how cond.wait() is written in the Safe library. It should wrap pthread_cond_wait(), which handles the mutex release/re-acquire automatically. A correct implementation would look like this:
func wait() { pthread_cond_wait(&cond, &mutex.pthreadMutex) }
If the library's wait() method doesn't use pthread_cond_wait() (or the Darwin equivalent) and instead tries to manually unlock/lock, that's the root cause.
2. Fix the Backpressure Notification
Ensure that every time the receiver removes a message from msgs (in your test, when it reads from c and writes to b), it triggers a broadcast() on the channel's condition variable. If the receiver isn't notifying waiting senders, they'll stay stuck in wait() forever.
3. Workaround for iOS 12
If the issue is specific to iOS 12's pthread behavior, you can try manually unlocking/re-locking around the wait (though this is a workaround, not a proper fix):
// Inside the send() method's while loop: while msgs.count > cap { cond.mutex.unlock() // Explicitly release the mutex before waiting cond.wait() cond.mutex.lock() // Re-acquire after waking up }
This forces the mutex to be released while waiting, preventing multiple threads from holding it at once.
4. Build a Minimal Channel for Comparison
Since your standalone mutex test works, build a tiny channel implementation using the same Mutex and condition variable logic. If your version doesn't deadlock, compare it line-by-line with the Safe library's Chan code to spot differences in lock ordering or notification timing.
Next Steps
Since the Safe library is read-only, you'll need to fork the repo and apply fixes locally. The most critical fix is ensuring the condition variable's wait() method uses the atomic unlock/wait/lock behavior provided by pthread primitives.
内容的提问来源于stack exchange,提问作者Erhannis

