C++11 condition_variable条件变量的用法说明

 更新时间:2022年07月11日 15:03:04   作者:kingforyang  
这篇文章主要介绍了C++11 condition_variable条件变量的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

1 什么是条件变量

condition_variable是一个类,常和mutex搭配使用。

condition_variable类是一个同步原语,可用于阻塞一个线程或同时阻止多个线程,直到另一个线程修改共享变量并通知condition_variable。

防止多线程场景下,共享变量混乱。

理解条件变量要先理解三个概念:

  • 锁 (锁住共享变量,线程独占)
  • wait 等待 (等待通知条件变量,变化的共享变量是否满足条件)
  • notify 通知 (通知等待的条件变量,共享变量发送变化)

2 condition_variable类定义

2.1 wait函数

void wait( std::unique_lockstd::mutex& lock );
//Predicate是lambda表达式。
template< class Predicate >
void wait( std::unique_lockstd::mutex& lock, Predicate pred );
//以上二者都被notify_one())或notify_broadcast()唤醒,但是
//第二种方式是唤醒后也要满足Predicate的条件。
//如果不满足条件,继续解锁互斥量,然后让线程处于阻塞或等待状态。
//第二种等价于
while (!pred())
{
wait(lock);
}

3 condition_variable用法

condition_variable必定至少有两方,一方是资源修改线程,一方是资源等待线程。就跟打篮球一样,同时篮球只会在一个人手中,投篮后就释放了篮球所有权,其他方就会抢夺篮球所有权。

3.1 资源修改线程步骤

  • 获取一个mutex使用 std::unique_lock< std::mutex >
  • 保持锁定状态,修改共享变量
  • condition_variable对象执行notify_one或者notify_all(notify_one/notify_all执行前可以释放锁)

3.2 资源等待线程步骤

  • 获取一个mutex使用 std::unique_lock< std::mutex > unlock用于保护要修改的共享变量
  • 检查条件变量,

(1)条件变量满足,线程继续执行

(2)条件变量不满足,wait会释放unlock锁,并挂起线程。

  • 当notify通知条件变量、超时过期或发生虚假唤醒时,线程被唤醒,互斥锁unlock被原子地重新获取。然后,线程应该检查条件,如果唤醒是假的,则继续等待

4 代码示例

4.1 无需notify场景

当wait第一次执行是,条件已经满足,则程序不会阻塞(即无需notify),会直接向下执行。(仅为说明3.2 中第2点(1)的情况)

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::cout << "3、worker_thread子线程开始执行"  << endl;
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    std::cout << "4、worker_thread子线程获取到锁,条件满足无需notify,不阻塞向下执行"  << endl;
    cv.wait(lk, []{return ready;});
 
    // after the wait, we own the lock.
    data += " after processing";
    // Send data back to main()
    processed = true;
    std::cout << "5、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();
    std::cout << "6、worker_thread子线程交出执行权限,主线程执行"  << endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
    
    cv.notify_one();
    std::cout << "9、worker_thread调用 notify_one"  << endl;
}
int main()
{
    std::thread worker(worker_thread);
    std::cout << "1、主线程开始执行"  << std::endl;
    data = "Example data";
    // send data to the worker thread
    {
        //std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::lock_guard<std::mutex> lk(m);
        ready = true;
    }
    std::cout << "2、锁已经释放了,主线程休眠,子线程执行"  << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    //cv.notify_one();
    {
        std::cout << "7、主线程data:" << data << endl;
        std::unique_lock<std::mutex> lk(m);
        std::cout << "8、主线程条件满足无需notify" << endl;
        cv.wait(lk, []{return processed;});
    }
    
    worker.join();
     std::cout << "10、主线程结束" << endl;
}

执行结果:

4.2 正常应用场景1

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::cout << "3、worker_thread子线程开始执行"  << endl;
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    std::cout << "4、worker_thread子线程获取到锁,条件不满足,释放lk锁,子线程阻塞"  << endl;
    cv.wait(lk, []{return ready;});
    std::cout << "8、worker_thread子线程获取到锁,子线程继续执行"  << endl;
    // after the wait, we own the lock.
    data += " after processing";
    // Send data back to main()
    processed = true;
    std::cout << "9、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();
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    std::cout << "10、worker_thread调用 notify_one通知主线程执行"  << endl;
    cv.notify_one();
}
int main()
{
    std::thread worker(worker_thread);
    std::cout << "1、主线程开始执行"  << std::endl;
    data = "Example data";
    // send data to the worker thread
    {
        std::cout << "2、主线程休眠,子线程进入执行"  << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::cout << "5、主线程结束休眠,主线程获取lk锁,进入执行"  << std::endl;
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        
    }
    std::cout << "6、主线程释放lk,调用notify通知子线程"  << std::endl;
    cv.notify_one();
    {
        std::cout << "7、由于主线程的执行时钟周期未结束,继续执行主线程获取lk, wait检查条件不满足,释放锁" << endl;
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
   
    worker.join();
     std::cout << "11、主线程结束" << endl;
}

执行结果:

这里notify执行后不一定立即执行子线程,如果cpu执行时钟周期未结束,则主线程会继续执行. 所以7,8,9,10顺序可能变化参见4.3

同时4.1也会因为cpu时钟周期,执行顺序有所变动

4.3 正常应用场景2

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::cout << "3、worker_thread子线程开始执行"  << endl;
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    std::cout << "4、worker_thread子线程获取到锁,条件不满足,释放lk锁,子线程阻塞"  << endl;
    cv.wait(lk, []{return ready;});
    std::cout << "8、worker_thread子线程获取到锁,子线程继续执行"  << endl;
    // after the wait, we own the lock.
    data += " after processing";
    // Send data back to main()
    processed = true;
    std::cout << "9、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();
    std::cout << "10、worker_thread调用 notify_one通知主线程执行"  << endl;
    cv.notify_one();
}
int main()
{
    std::thread worker(worker_thread);
    std::cout << "1、主线程开始执行"  << std::endl;
    data = "Example data";
    // send data to the worker thread
    {
        std::cout << "2、主线程休眠,子线程进入执行"  << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::cout << "5、主线程结束休眠,主线程获取lk锁,进入执行"  << std::endl;
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        
    }
    std::cout << "6、主线程释放lk,调用notify通知子线程"  << std::endl;
    cv.notify_one();
    {
        for(int i = 0; i< 10000000; i++)
        {
            int j = i;
        }
        std::cout << "7、由于主线程的执行时钟周期未结束,继续执行主线程获取lk, wait检查条件不满足,释放锁" << endl;
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    
    worker.join();
    std::cout << "11、主线程结束" << endl;
}

执行结果:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。 

相关文章

  • 详解NSString 与C++ string字符串的互转

    详解NSString 与C++ string字符串的互转

    这篇文章主要介绍了详解NSString 与C++ string字符串的互转的相关资料,需要的朋友可以参考下
    2017-07-07
  • 浅谈VS中添加头文件时显示无法找到文件的问题

    浅谈VS中添加头文件时显示无法找到文件的问题

    下面小编就为大家带来一篇浅谈VS中添加头文件时显示无法找到文件的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • Qt实现拖拽功能图文教程(支持拖放文件、拖放操作)

    Qt实现拖拽功能图文教程(支持拖放文件、拖放操作)

    这篇文章主要给大家介绍了关于Qt实现拖拽功能(支持拖放文件、拖放操作)的相关资料,Qt是一款多平台的C++应用程序开发框架,它的独特之处在于可以快速开发出拖放式的开发桌面程序,需要的朋友可以参考下
    2023-11-11
  • vs2022项目文件夹内.vs文件夹容量虚高问题的解决

    vs2022项目文件夹内.vs文件夹容量虚高问题的解决

    经常会发现VS的项目文件夹占用空间很大,本文主要介绍了vs2022项目文件夹内.vs文件夹容量虚高问题的解决,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • C语言打印杨辉三角示例汇总

    C语言打印杨辉三角示例汇总

    本文给大家汇总了2种使用C语言实现杨辉三角的方法,不过还是给大家推荐第一种,希望大家能够喜欢。
    2016-02-02
  • C++中using的三种用法举例详解

    C++中using的三种用法举例详解

    最近在使用中,发现了一种以前没学过的using用法,于是在这里,将using的几种用法总结一下,下面这篇文章主要给大家介绍了关于C++中using的三种用法,需要的朋友可以参考下
    2023-02-02
  • C语言实现恶作剧关机程序

    C语言实现恶作剧关机程序

    大家好,本篇文章主要讲的是C语言实现恶作剧关机程序,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • C++使用文件实现学生信息管理系统

    C++使用文件实现学生信息管理系统

    这篇文章主要为大家详细介绍了C++使用文件实现学生信息管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • C语言实现停车场项目

    C语言实现停车场项目

    这篇文章主要为大家详细介绍了C语言实现停车场项目,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C++仿函数的概念优点和使用

    C++仿函数的概念优点和使用

    这篇文章主要介绍了C++仿函数的概念优点和使用,在C++中,仿函数是指重载了operator的类或结构体的对象,它们的行为类似于普通函数,因此可以像函数一样被调用,需要的朋友可以参考下
    2025-04-04

最新评论