C/C++ Wait Morphing锁内通知 锁外通知

cpp 复制代码
template<typename T>
class BlockingQueue : noncopyable
{
 public:
 // 使用deque的原因:前插,尾插为O(1)消息队列尾巴放,前面拿
 // 为什么不list? 
 // 因为deque有良好的缓存性,cache缓存,list 地址不同
  using queue_type = std::deque<T>;

  BlockingQueue()
    : mutex_(),
      notEmpty_(mutex_),
      queue_()
  {
  }

//实现左值右值
  void put(const T& x)
  {
    MutexLockGuard lock(mutex_);
    queue_.push_back(x); //尾插
    notEmpty_.notify(); // wait morphing saves us
    // http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/
  }

  void put(T&& x)
  {
    MutexLockGuard lock(mutex_);
    queue_.push_back(std::move(x));
    notEmpty_.notify();
  }

  T take()
  {
    MutexLockGuard lock(mutex_);
    // always use a while-loop, due to spurious wakeup
    while (queue_.empty())
    {
      notEmpty_.wait();
    }
    assert(!queue_.empty());
    T front(std::move(queue_.front()));
    queue_.pop_front();
    return front;
  }

  queue_type drain()
  {
    std::deque<T> queue;
    {
      MutexLockGuard lock(mutex_);
      queue = std::move(queue_);
      assert(queue_.empty());
    }
    return queue;
  }

  size_t size() const
  {
    MutexLockGuard lock(mutex_);
    return queue_.size();
  }

 private:
  mutable MutexLock mutex_;
  Condition         notEmpty_ GUARDED_BY(mutex_);
  queue_type        queue_ GUARDED_BY(mutex_);
};  // __attribute__ ((aligned (64)));

}  // namespace muduo

#endif  // MUDUO_BASE_BLOCKINGQUEUE_H

锁内通知(notify with mutex locked) :通知时当前线程持有锁,可能导致被唤醒的线程立即尝试获取锁,但因为锁还未释放,唤醒的线程会立即重新睡眠(spurious wakeup 或调度开销)。锁外通知(notify without mutex locked):通知时锁已释放,可能导致信号丢失(lost signal),即通知发生在等待线程检查条件之前。

  • 锁内通知(notify with mutex locked):通知时当前线程持有锁,可能导致被唤醒的线程立即尝试获取锁,但因为锁还未释放,唤醒的线程会立即重新睡眠(spurious wakeup 或调度开销)。
  • 锁外通知(notify without mutex locked):通知时锁已释放,可能导致信号丢失(lost signal),即通知发生在等待线程检查条件之前。

"Wait Morphing" 是什么?

当一个线程在持有锁时调用 notify,线程库不会立即唤醒等待线程并让其竞争锁。相反,线程库会将等待线程的 "等待状态" 直接转换为 "就绪状态",并在当前线程释放锁时无缝地将锁交给被唤醒的线程。这种优化避免了被唤醒线程立即竞争锁失败并重新睡眠的情况,减少了上下文切换和调度开销。

  • 当一个线程在持有锁时调用 notify,线程库不会立即唤醒等待线程并让其竞争锁。
  • 相反,线程库会将等待线程的 "等待状态" 直接转换为 "就绪状态",并在当前线程释放锁时无缝地将锁交给被唤醒的线程。
  • 这种优化避免了被唤醒线程立即竞争锁失败并重新睡眠的情况,减少了上下文切换和调度开销。