条件变量是一种同步机制,用于多线程编程中,当某个条件不满足时,使线程阻塞,并在条件满足时被唤醒。通常与互斥锁一起使用,以确保对共享资源的互斥访问和防止竞态条件。

condition_variable

构造函数

  • 只支持默认构造;
  • 不支持拷贝构造和移动构造。

成员函数

  • wait:阻塞当前线程,直到条件变量被唤醒。
  • wait_until:阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点。
  • wait_for:阻塞当前线程,直到条件变量被唤醒,或直到指定时限时长后。
  • notify_one:通知一个等待的线程。
  • notify_all:通知所有等待的线程。

一般用法

  • 对于等待条件满足的线程:
    1. 获得std::mutex,通常使用std::unique_lock;
    2. 使用wait,wait_for,wait_until等待唤醒,此时线程释放互斥,并进入阻塞;
    3. 被其他线程唤醒,被虚假唤醒或者等待超时,线程自动重获得互斥,此时应检查条件是否成立,若不成立,则继续等待或者返回;
  • 对于使条件满足后通知其他线程的线程:
    1. 获得std::mutex,通常使用std::lock_guard;
    2. 在成功获得锁后,使条件满足;
    3. 使用 notify_one 或 notify_all 唤醒等待的线程;

wait(lock)用法

void wait(std::unique_lock<std::mutex>& lock);

调用wait(lock)行为:

  1. 阻塞和释放锁: wait函数会将当前线程阻塞,并自动释放互斥锁。
  2. 等待通知: 线程进入阻塞状态,等待其他线程调用notify_one或notify_all函数来通知条件变量。
  3. 唤醒和重新获取锁: 当其他线程调用notify_one或notify_all后,一个或所有等待的线程会被唤醒。唤醒后,wait会尝试重新获取之前释放的互斥锁。
  4. 直到wait获取到之前释放的互斥锁后,线程继续往下执行。

wait(lock, pred)用法

template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);

// 相当于
while (!pred()) {
    wait(lock);
}

调用wait(lock, pred)行为:

  1. 检查谓词:wait 函数首先会调用谓词pred()
    1. 如果pred()返回true,表示条件已经满足。wait 函数会立即返回,不进行任何操作。调用线程继续持有锁,并从 wait 函数返回。
    2. 如果pred()返回false,表示条件尚未满足,执行以下步骤。
  2. 阻塞和释放锁: wait函数会将当前线程阻塞,并自动释放互斥锁。
  3. 等待通知: 线程进入阻塞状态,等待其他线程调用notify_one或notify_all函数来通知条件变量。
  4. 唤醒和重新获取锁: 当其他线程调用notify_one或notify_all后,一个或所有等待的线程会被唤醒。唤醒后,wait会尝试重新获取之前释放的互斥锁。
  5. 条件检查: 重新获取锁后,wait继续调用谓词pred()。
    1. 如果pred()返回true,则线程继续往下执行;
    2. 如果返回false,则说明条件可能还未满足,回到第2步继续执行。

虚假唤醒

虚假唤醒是指线程在等待条件变量时,在条件尚未满足的情况下被唤醒。这种情况下,线程会从wait()函数返回,但实际上并没有被其他线程通知。虚假唤醒是多线程编程中可能出现的正常现象。

虚假唤醒的原因:

  • 操作系统或库的内部实现: 操作系统或C++标准库的实现可能会在内部执行一些资源管理或线程调度操作,这可能导致线程被唤醒,即使没有收到显式的通知。
  • 硬件中断: 硬件中断也可能导致线程被唤醒,即使条件尚未满足。
  • 在某些情况下,多个线程可能同时等待同一个条件变量,当其中一个线程被唤醒并满足条件后,其他线程也可能会被唤醒,即使它们的条件尚未满足。

如何避免虚假唤醒:

  • 在wait(lock)函数返回后,再检查是否满足条件。
  • 使用wait(lock, pred)函数。

condition_variable_any

condition_variable_any用法与condition_variable基本相同,除了:

  • condition_variable_any可以与任何可锁定类型(如std::mutex、std::shared_mutex、std::recursive_mutex等)一起使用。
  • condition_variable只能与unique_lock<mutex>配合使用。

notify_all_at_thread_exit

std::notify_all_at_thread_exit()用于在线程退出时通知其他等待的线程。其核心作用是保证在线程退出时,所有等待该线程完成的线程都能被正确唤醒,避免资源泄漏和死锁的发生。

void notify_all_at_thread_exit(std::condition_variable& cond, std::unique_lock<std::mutex> lock)

notify_all_at_thread_exit()内部会接管传入的互斥锁,并在线程退出时自动调用lock.unlock()释放它。

当线程退出时,notify_all_at_thread_exit会自动调用cond.notify_all()唤醒所有等待的线程。