admin管理员组文章数量:1599532
简述
在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量。
c++11提供的 condition_variable 类是一个同步原语,它能够阻塞一个或者多个线程,直到另一线程修改共享变量并通知 condition_variable。
也可以把它理解为信号通知机制,一个线程负责发送信号,其他线程等待该信号的触发。
condition_variable 存在一些问题,如虚假唤醒,这可以通知增加额外的共享变量来避免。
针对增加额外的变量这一点,为什么不在另一线程循环检测这个变量,从而达到相同目的而不需要再使用条件变量?
- 循环检测时,程序在高速运行,占用过高的cpu,而条件变量的等待是阻塞,休眠状态下cpu使用率为0,省电!
- 对于运行中的线程,可能会被操作系统调度,切换cpu核心,这样一来,所有的缓存可能失效,而条件变量不会,省时!
对于只需要通知一次的情况,如初始化完成、登录成功等,建议不要使用 condition_variable,使用std::future更好。
使用
通知方:
- 获取 std::mutex, 通常是 std::lock_guard
- 修改共享变量(即使共享变量是原子变量,也需要在互斥对象内进行修改,以保证正确地将修改发布到等待线程)
- 在 condition_variable 上执行 notify_one/notify_all 通知条件变量(该操作不需要锁)
等待方:
- 获取相同的 std::mutex, 使用 std::unique_lock
- 执行 wait,wait_for或wait_until(该操作会自动释放锁并阻塞)
- 接收到条件变量通知、超时或者发生虚假唤醒时,线程被唤醒,并自动获取锁。唤醒的线程负责检查共享变量,如果是虚假唤醒,则应继续等待
std :: condition_variable仅适用于 std::unique_lock, 此限制允许在某些平台上获得最大效率。 std :: condition_variable_any提供可与任何BasicLockable对象一起使用的条件变量,例如std :: shared_lock。
示例代码如下:
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
输出:
main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing
其中:
- notify_one() 只唤醒一个线程,如果有多个线程,具体唤醒哪一个不确定,如果需要唤醒其他所有线程,使用 notify_all()
- 执行 notify_one() 时不需要锁
- 修改共享变量 ready/processed 时需要锁,共享变量用于避免虚假唤醒
- cv.wait 第一个参数必须是 unique_lock,因为它内部会执行 unlock和lock,如果需要设置超时,使用 wait_for/wait_until
总结
使用注意事项:
- 需要共享变量来避免虚假唤醒
- 共享变量的修改需要在锁内进行
- 通知线程在发出通知时不需要加锁
- 通知线程一般使用lock_guard即可
- 接收线程的wait函数第一个参数需要是unique_lock
- 接收线程需要判断是否为虚假唤醒
特别注意,如果不使用共享变量,当通知线程在接收线程准备接收之前发送通知,接收线程将要永远阻塞了。这里,共享变量已经置位,所以它也能避免丢失唤醒。
总之,条件变量使用广泛,了解它的优势和不足,便于我们作出最佳决策。
参考资料
std::condition_variable
why do I need std::condition_variable?
本文标签: 变量多线程条件conditionvariable
版权声明:本文标题:c++11多线程编程同步——使用条件变量condition variable 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dongtai/1728323206a1154096.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论