C++开发基础之理解std::condition_variable中的wait与wait_for的区别与使用场景
前言
在多线程编程中,线程之间的同步是一个重要的课题。std::condition_variable
提供了一种机制,允许线程在某些条件满足之前进行等待。我们通常使用 wait
和 wait_for
等函数来控制线程的阻塞和唤醒。虽然这两个函数看起来类似,但它们之间存在一些重要的区别。在本文中,我们将详细介绍这两者的差异以及它们适用的场景。
std::condition_variable::wait
:无限等待
1. 基本功能:
m_cv.wait(lock)
是条件变量最常见的使用方式。它的作用是让当前线程进入阻塞状态,直到以下两个条件之一发生:
- 条件变量被通知(
notify_one()
或notify_all()
被调用)。 - 条件变量的状态满足某个条件,通常需要在等待前和后检查某个共享数据。
当线程调用 wait
时,它会释放与之相关联的锁 (std::unique_lock
或 std::lock_guard
),允许其他线程在等待期间修改共享数据。当通知发生时,线程会重新获得锁,并继续执行。
2. 代码示例:
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock); // 阻塞当前线程直到被通知
3. 适用场景:
m_cv.wait(lock)
非常适用于以下场景:
- 你希望线程在没有条件满足时保持等待状态,直到其他线程调用通知。
- 适合于生产者-消费者模型,或者任何需要等待特定条件触发的同步场景。
4. 注意事项:
- 由于该函数会阻塞线程直到通知,因此它可能会导致线程长时间处于等待状态,这时我们通常会在
wait
调用之前判断条件,以确保线程只有在真正需要等待时才进入阻塞。
std::condition_variable::wait_for
:带超时的等待
1. 基本功能:
与 wait
不同,m_cv.wait_for(lock, std::chrono::milliseconds(100))
允许我们在等待时设置超时时间。在这个例子中,当前线程会等待最多 100 毫秒,或者直到被通知为止。
如果超时后线程仍然没有收到通知,wait_for
会自动返回 false
,表示超时发生。否则,如果条件满足,或者收到了通知,它会返回 true
。
2. 代码示例:
std::unique_lock<std::mutex> lock(m_mutex);
bool notified = m_cv.wait_for(lock, std::chrono::milliseconds(100)); // 等待最多100毫秒
if (!notified) {std::cout << "超时未收到通知" << std::endl;
} else {std::cout << "收到通知" << std::endl;
}
3. 适用场景:
m_cv.wait_for
在以下情况下非常有用:
- 你希望给等待的线程设置一个最长等待时间,避免线程在无条件满足时一直阻塞。
- 适用于一些需要响应超时的应用,比如定时任务,或者在一定时间内需要获取资源的场景。
4. 注意事项:
- 如果设置了超时时间,线程在超时后会返回。这时,我们可以根据返回值来决定是否继续执行,或者处理超时的逻辑。
- 超时机制避免了死锁的风险,但也可能导致线程在超时后执行不完整的任务,因此需要在应用中合理使用。
区别总结
特性 | m_cv.wait(lock) | m_cv.wait_for(lock, timeout) |
---|---|---|
超时控制 | 无限等待,直到收到通知 | 设置一个最大等待时间,超时后返回 false |
返回值 | 没有返回值,线程在通知时继续执行 | 返回 true 或 false ,表示是否在超时前收到通知 |
使用场景 | 适合需要等待某个条件或事件发生的场景 | 适合需要控制等待时间,避免死锁的场景 |
阻塞行为 | 无限阻塞直到条件满足 | 阻塞指定时间后自动返回 |
什么时候使用 wait
,什么时候使用 wait_for
?
- 使用
wait
: 当你希望线程在没有收到通知之前一直阻塞,并且没有必要关心等待的最大时间时,使用wait
。 - 使用
wait_for
: 当你希望线程在等待时能够响应超时,或者希望限制等待时间,避免线程长时间处于阻塞状态时,使用wait_for
。
总结
std::condition_variable
提供了强大的线程同步能力,尤其是在处理多线程之间的通知和协调时非常有用。理解 wait
和 wait_for
之间的区别,并根据实际需求选择合适的等待方式,是编写高效且安全的多线程代码的关键。在选择这两者时,你需要考虑是否需要超时机制,以及如何处理超时后可能的错误或恢复逻辑。