C++11互斥锁读写语义(多读者/单写者同步)技术咨询
Hey, great question—upgrading old sync code to modern C++ is a smart move for portability, and that concise MRSW implementation you found is definitely worth unpacking. Let's go through your concerns one by one:
1. 内存模型合规性与内存可见性
First off, this implementation is 100% compliant with C++11 and later memory models—no hidden visibility issues here. Here's why:
- Every access to
readers_andwriting_is guarded bystd::mutex, which enforces strict happens-before relationships. Any changes made while holding the mutex are guaranteed to be visible to threads that lock the mutex afterward. - The
std::condition_variable::wait()call automatically releases the mutex and re-acquires it when awakened, so the lambda check (!writing_orreaders_ == 0 && !writing_) will always see the latest state of those shared variables. No stale reads or data races to worry about.
2. 惊群效应与优化
You're right to flag the notify_all() call in unlock_write()—that does cause a thundering herd problem. All waiting reader threads wake up, but only one can grab the mutex and increment readers_; the rest will immediately go back to waiting, wasting CPU cycles on context switches.
Two solid fixes for this:
- Use two condition variables: Add a
cv_writer_for writers waiting on readers, andcv_readers_for readers waiting on writers. When unlocking a writer, only notifycv_readers_; when the last reader unlocks, only notifycv_writer_. This targets wakeups precisely, eliminating the herd. - Upgrade to C++17 and use
std::shared_mutex: The standard library's implementation already handles this optimization under the hood, leveraging platform-specific primitives to avoid unnecessary wakeups.
3. Performance vs. std::shared_mutex
Let's compare this custom implementation to C++17's std::shared_mutex:
Pros of the custom code
- C11/C14 compatibility: If your project can't yet move to C++17, this works perfectly without relying on newer standard features.
- Full control: You can tweak the logic for edge cases specific to your use case (like adding recursive lock support, or adjusting waiting behavior) without digging into standard library internals.
Cons of the custom code
- Subpar performance: Standard library implementations are heavily optimized by compiler vendors. They often use lighter atomic operations alongside condition variables, or leverage OS-level sync primitives that are faster than a naive mutex+condvar setup. In high-concurrency scenarios,
std::shared_mutexwill almost always have better throughput and lower latency. - Missing edge-case handling: The standard library takes care of things like thread cancellation safety, edge cases around spurious wakeups, and subtle memory ordering details that are easy to miss in a custom implementation.
4. Unhandled Boundary Conditions
Your implementation works for basic cases, but there are a few edge scenarios it doesn't cover:
- Recursive read locks: If a thread calls
lock_read()multiple times,readers_will keep incrementing, and when you unlock, you'll end up with a negativereaders_value (undefined behavior). Fix this by tracking per-thread read counts with athread_localvariable—only incrementreaders_if the thread doesn't already hold a read lock. - Writer-to-reader downgrade: If a thread holding a write lock tries to acquire a read lock, it'll deadlock (since
writing_is true, thewait()inlock_read()will block forever). To support downgrading, add logic to check if the current thread is the writer, and allow the read lock without waiting if so. - Exception safety: While the current code is mostly safe (if
wait()throws,readers_wasn't incremented yet), if you add any additional logic inside the lock guards, you'll need to ensure exceptions don't leave the shared state in a broken state.
Final Takeaway
This is a great lightweight MRSW implementation for C11/C14 environments. But if you can move to C++17 or later, std::shared_mutex is the way to go—it's more performant, handles edge cases out of the box, and reduces the amount of custom code you need to maintain.
内容的提问来源于stack exchange,提问作者frasnian

