c++11多线程编程同步——使用条件变量condition variable

简述

在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量。

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?

  • 7
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中,我们可以使用线程库来实现多线编程线程的挂起、唤醒与终止是多线编程中非常重要的一部分。 线程的挂起也称为线程的休眠,它可以让线程停止运行一段时间,等待某个条件满足后再继续运行。在 C++ 中,我们可以使用 std::this_thread::sleep_for() 函数来实现线程的挂起,该函数可以让当前线程挂起一段时间,例如: ```cpp #include <chrono> #include <thread> int main() { // 挂起当前线程 1 秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; } ``` 线程的唤醒可以通过条件变量来实现,条件变量是一种同步机制,用于在线程之间传递信号。在 C++ 中,我们可以使用 std::condition_variable 类来创建条件变量,然后使用 wait() 函数来挂起线程等待条件变量的信号,使用 notify_one() 函数来唤醒一个等待条件变量线程,例如: ```cpp #include <condition_variable> #include <mutex> #include <thread> std::condition_variable cv; std::mutex mtx; bool ready = false; void worker_thread() { // 等待条件变量的信号 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); // 条件满足后继续执行 // ... } int main() { // 唤醒等待条件变量线程 { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); return 0; } ``` 线程的终止可以使用 std::thread::join() 函数来实现,该函数可以让当前线程等待另一个线程执行完成后再继续执行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 等待 worker_thread 执行完成 t.join(); return 0; } ``` 另外,线程的终止还可以使用 std::thread::detach() 函数来实现,该函数可以让当前线程与创建的线程分离,使得两个线程可以独立运行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 分离线程,使得两个线程可以独立运行 t.detach(); return 0; } ``` 需要注意的是,分离线程后,主线程不能再使用 join() 函数等待子线程执行完成,否则会导致程序崩溃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值