精华内容
下载资源
问答
  • C++11条件变量使用详解

    万次阅读 多人点赞 2019-05-02 00:31:21
    C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。 其主要成员函数如下: 条件变量是利用线程间共享...

    condition_variable介绍

    在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。

    其主要成员函数如下:
    在这里插入图片描述
    条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

    1. 一个线程因等待"条件变量的条件成立"而挂起;
    2. 另外一个线程使"条件成立",给出信号,从而唤醒被等待的线程。

    为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁 只能是 std::unique_lock<std::mutex> RAII模板类

    上面提到的两个步骤,分别是使用以下两个方法实现:

    • 等待条件成立使用的是condition_variable类成员wait 、wait_for 或 wait_until。
    • 给出信号使用的是condition_variable类成员notify_one或者notify_all函数。

    细节说明

    在条件变量中只能使用std::unique_lock<std::mutex>说明

    unique_lock和lock_guard都是管理锁的辅助类工具,都是RAII风格;它们是在定义时获得锁,在析构时释放锁。它们的主要区别在于unique_lock锁机制更加灵活,可以再需要的时候进行lock或者unlock调用,不非得是析构或者构造时。它们的区别可以通过成员函数就可以一目了然。
    在这里插入图片描述

    wait/wait_for说明

    线程的阻塞是通过成员函数wait()/wait_for()/wait_until()函数实现的。这里主要说明前面两个函数:

    • wait()成员函数

      函数声明如下:

      void wait( std::unique_lock<std::mutex>& lock );
      //Predicate 谓词函数,可以普通函数或者lambda表达式
      template< class Predicate >
      void wait( std::unique_lock<std::mutex>& lock, Predicate pred );
      

      wait 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可选地循环直至满足某谓词

    • wait_for()成员函数

      函数声明如下:

      1)
      template< class Rep, class Period >
      std::cv_status wait_for( std::unique_lock<std::mutex>& lock,
                               const std::chrono::duration<Rep, Period>& rel_time);2)
      template< class Rep, class Period, class Predicate >
      bool wait_for( std::unique_lock<std::mutex>& lock,
                     const std::chrono::duration<Rep, Period>& rel_time,
                     Predicate pred);
      

      wait_for 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,或者超时返回

      返回值说明:

      (1)若经过 rel_time 所指定的关联时限则为 std::cv_status::timeout ,否则为 std::cv_status::no_timeout 。

      (2)若经过 rel_time 时限后谓词 pred 仍求值为 false 则为 false ,否则为 true 。

    以上两个类型的wait函数都在会阻塞时,自动释放锁权限,即调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,否则当前线程一直占有锁,线程被阻塞。

    notify_all/notify_one

    notify函数声明如下:

    void notify_one() noexcept;
    若任何线程在 *this 上等待,则调用 notify_one 会解阻塞(唤醒)等待线程之一。
    
    void notify_all() noexcept;
    若任何线程在 *this 上等待,则解阻塞(唤醒)全部等待线程。
    

    虚假唤醒

    在正常情况下,wait类型函数返回时要不是因为被唤醒,要不是因为超时才返回,但是在实际中发现,因此操作系统的原因,wait类型在不满足条件时,它也会返回,这就导致了虚假唤醒。因此,我们一般都是使用带有谓词参数的wait函数,因为这种(xxx, Predicate pred )类型的函数等价于:

    while (!pred()) //while循环,解决了虚假唤醒的问题
    {
        wait(lock);
    }
    

    原因说明如下:

    假设系统不存在虚假唤醒的时,代码形式如下:

    if (不满足xxx条件)
    {
        //没有虚假唤醒,wait函数可以一直等待,直到被唤醒或者超时,没有问题。
        //但实际中却存在虚假唤醒,导致假设不成立,wait不会继续等待,跳出if语句,
        //提前执行其他代码,流程异常
        wait();  
    }
    
    //其他代码
    ...
    

    正确的使用方式,使用while语句解决:

    while (!(xxx条件) )
    {
        //虚假唤醒发生,由于while循环,再次检查条件是否满足,
        //否则继续等待,解决虚假唤醒
        wait();  
    }
    //其他代码
    ....
    

    条件变量使用

    在这里,我们使用条件变量,解决生产者-消费者问题,该问题主要描述如下:

    生产者-消费者问题,也称有限缓冲问题,是一个多进程/线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程/线程——即所谓的“生产者”和“消费者”,在实际运行时会发生的问题。

    生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

    要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据

    同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者

    生产者-消费者代码如下:

    std::mutex g_cvMutex;
    std::condition_variable g_cv;
    
    //缓存区
    std::deque<int> g_data_deque;
    //缓存区最大数目
    const int  MAX_NUM = 30;
    //数据
    int g_next_index = 0;
    
    //生产者,消费者线程个数
    const int PRODUCER_THREAD_NUM  = 3;
    const int CONSUMER_THREAD_NUM = 3;
    
    void  producer_thread(int thread_id)
    {
    	 while (true)
    	 {
    	     std::this_thread::sleep_for(std::chrono::milliseconds(500));
    	     //加锁
    	     std::unique_lock <std::mutex> lk(g_cvMutex);
    	     //当队列未满时,继续添加数据
    	     g_cv.wait(lk, [](){ return g_data_deque.size() <= MAX_NUM; });
    	     g_next_index++;
    	     g_data_deque.push_back(g_next_index);
    	     std::cout << "producer_thread: " << thread_id << " producer data: " << g_next_index;
    	     std::cout << " queue size: " << g_data_deque.size() << std::endl;
    	     //唤醒其他线程 
    	     g_cv.notify_all();
    	     //自动释放锁
    	 }
    }
    
    void  consumer_thread(int thread_id)
    {
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(550));
            //加锁
            std::unique_lock <std::mutex> lk(g_cvMutex);
            //检测条件是否达成
            g_cv.wait( lk,   []{ return !g_data_deque.empty(); });
            //互斥操作,消息数据
            int data = g_data_deque.front();
            g_data_deque.pop_front();
            std::cout << "\tconsumer_thread: " << thread_id << " consumer data: ";
            std::cout << data << " deque size: " << g_data_deque.size() << std::endl;
            //唤醒其他线程
            g_cv.notify_all();
            //自动释放锁
        }
    }
    
    
    int main()
    {
        std::thread arrRroducerThread[PRODUCER_THREAD_NUM];
        std::thread arrConsumerThread[CONSUMER_THREAD_NUM];
    
        for (int i = 0; i < PRODUCER_THREAD_NUM; i++)
        {
            arrRroducerThread[i] = std::thread(producer_thread, i);
        }
    
        for (int i = 0; i < CONSUMER_THREAD_NUM; i++)
        {
            arrConsumerThread[i] = std::thread(consumer_thread, i);
        }
    
        for (int i = 0; i < PRODUCER_THREAD_NUM; i++)
        {
            arrRroducerThread[i].join();
        }
    
        for (int i = 0; i < CONSUMER_THREAD_NUM; i++)
        {
            arrConsumerThread[i].join();
        }
        
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述

    展开全文
  • C++11条件变量的理解(逻辑)

    千次阅读 2017-02-17 22:53:05
    说到这里,解惑来源于这篇文章c++11线程之条件变量condition_variable,互斥锁只能保证线程不同时访问共享区资源,但是访问的顺序是杂乱无章的,而条件变量是为了保证线程操作按顺序进行。 还有,条件变量std::...

    之前对条件变量一直抱有疑惑,通过互斥锁已经能够保证同一时刻只有一个线程访问共享区资源了,那还要用条件变量干什么呢?说到这里,解惑来源于这篇文章c++11线程之条件变量condition_variable,互斥锁只能保证线程不同时访问共享区资源,但是访问的顺序是杂乱无章的,而条件变量是为了保证线程操作按顺序进行。
    还有,条件变量std::condition_variable::wait(std::unique& lck)函数在调用的时候会释放锁lck(调用lck.unlock()),在其他线程调用notify_all()(此处也可以是其他唤醒函数)后线程停止阻塞,继续执行,但是此时线程使用的wait()会将lck恢复到阻塞之前的状态,也就是加锁,之前一直百思不得其解,还一直纠结这个代码为什么没有死锁`#include // std::cout

    include // std::thread

    include // std::mutex, std::unique_lock

    include // std::condition_variable

    std::mutex mtx;
    std::condition_variable cv;

    int cargo = 0; // shared value by producers and consumers

    void consumer()
    {
    std::unique_lock < std::mutex > lck(mtx);
    while (cargo == 0)
    cv.wait(lck);//阻塞
    std::cout << cargo << ‘\n’;
    cargo = 0;
    }
    void producer(int id)
    {
    //std::unique_lock < std::mutex > lck(mtx);
    cargo = id;
    cv.notify_one();//唤醒某一个线程
    }

    int main()
    {
    std::thread consumers[10], producers[10];

    // spawn 10 consumers and 10 producers:
    for (int i = 0; i < 10; ++i) 
    {
        consumers[i] = std::thread(consumer);
        producers[i] = std::thread(producer, i + 1);
    }
    
    // join them back:
    for (int i = 0; i < 10; ++i) {
        producers[i].join();
        consumers[i].join();
    }
    return 0;
    

    }`,其实,条件变量在使得当前线程阻塞时,是拥有了一个锁的,如果不释放该锁,则当前线程会阻塞,而且其他线程也会因为该锁二阻塞,这就导致所有线程均阻塞,这是我们不想看到的结果,我们需要其他线程在当前线程阻塞时继续执行,从而触发当前线程被唤醒;在当前线程被唤醒后,需要加锁,这是因为当前线程已经进入资源共享区,需要放置其他线程也进来访问,所以加锁,如此便保证了顺序(逻辑上的顺序)访问。

    展开全文
  • c++11条件变量的使用,condition_variable

    千次阅读 2015-11-26 15:55:24
    void thread_prepare(int T) { std::lock_guard lk(mt); data_queue.push(T); data_con.notify_one(); } void thread_process() { while(1) { std::unique_lock lk(mt); data_con.wait(lk,[]{return !data_queue.
    void  thread_prepare(int T)
    {
    std::lock_guard<std::mutex> lk(mt);
    data_queue.push(T);
    data_con.notify_one();
    }
    

    void thread_process()
    {
    while(1)
    {
    std::unique_lock<std::mutex> lk(mt);
    data_con.wait(lk,[]{return !data_queue.empty();});
    std::cout<<data_queue.front()<<std::endl;
    data_queue.pop();
    lk.unlock();
    
    }
    }


    thread_process中,data_con的wait中,会持续等待notify,若没有Notify便会一直沉睡。

    即节省线程的时间片。

    展开全文
  • 一般来说,多线程中如果需要等待一个变量或者条件为true 或者同步多个线程,有两种方法:1 . 忙等待,不停地检查该变量是否满足条件while(pre) // polling loop {}该方式有很多缺点:占用cpu资源,... 使用条件变量 st

    一般来说,多线程中如果需要等待一个变量或者条件为true 或者同步多个线程,有两种方法:

    1 . 忙等待,不停地检查该变量是否满足条件

    while(pre)  // polling loop
    {}
    

    该方式有很多缺点:占用cpu资源,变量 pre 必须多线程安全,或者为 atomic 类型。在 while 中 sleep可以解决cpu占用问题,但是sleep不能在条件满足时及时的唤醒该线程。

    2 . 使用条件变量 std::condition_variable

    条件变量必须与 mutex 一起使用,mutex 的功能主要是保护共享数据,避免 race condition.

    条件变量主要有这几个成员方法

    // 存在虚假唤醒的可能,不推荐使用
    void wait (unique_lock<mutex>& lck);  // 阻塞该线程,直到被唤醒
    
    // 配合lamda函数,避免虚假唤醒——spurious wakeup
    template <class Predicate>  // 阻塞该线程,并且只有条件变为 true 的时候才能被唤醒
      void wait (unique_lock<mutex>& lck, Predicate pred);
      
    void notify_one();  // 唤醒一个等待的线程
    
    void notify_all();  // 唤醒所有等待的线程
    

    例如:

    	std::condition_variable cv;  // 全局的
    	std::mutex m; // 全局的
    	...
    	std::unique_lock<std::mutex> lk(m);
    	
    	// 阻塞,直到 my_flag == true 并且其他线程 notify 之后才返回
    	cv.wait(lk, []{ return my_flag; }); // 等价于下面带 while循环的代码块
    	...
    

    wait 有两个功能:1 阻塞该线程的执行,2 调用 lc.unlock() ,使得其他被该锁阻塞的线程(如果有)可以获得 mutex,并执行;当wait返回时(被唤醒)该线程再次自动获得 mutex (即独占的获得锁),当lk离开作用域时,再次 lk.unlock(),然后其他被唤醒的线程(如果有)可以获得这个锁。

    由于线程存在被虚假唤醒的可能,因此一般配合使用变量来防止被虚假唤醒,即使用带模板的 wait 函数: cv.wait(lk, [&]{ return my_flag == true; });
    其实就是如下代码的封装:

    	// 当虚假唤醒后,发现my_flag让然为false,继续调用wait
        while(!my_flag)
            cv.wait(lk);
    

    所以条件变量都是如下三个要素一起用:

    A condition_variable
    A mutex
    Some data guarded by the mutex

    举例:

    需要同步的区域可以用局部作用域来保护:

    #include <iostream>
    #include <condition_variable>
    #include <thread>
    #include <chrono>
     
    std::condition_variable cv;
    std::mutex cv_m; // This mutex is used for three purposes:
                     // 1) to synchronize accesses to i
                     // 2) to synchronize accesses to std::cerr
                     // 3) for the condition variable cv
    int i = 0;
     
    void waits()
    {
    	// {  // 建立一个局部作用域
        std::unique_lock<std::mutex> lk(cv_m);
        std::cerr << "Waiting... \n";
        cv.wait(lk, []{return i == 1;});
        // }
        std::cerr << "...finished waiting. i == 1\n";  // 如果这一句不需要同步,则可以去掉上面两个注释,即将mutex的保护范围缩小。
    }
     
    void signals()
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        {   
    	    // 若不需要同步 std::cerr 可以去掉该 lock_guard
            std::lock_guard<std::mutex> lk(cv_m);
            std::cerr << "Notifying...\n";
        }
        
        cv.notify_all();  // 唤醒所有等待cv的线程,但是由于 i!=1 所以不能真正唤醒。
     
        std::this_thread::sleep_for(std::chrono::seconds(1));
     
        {
            std::lock_guard<std::mutex> lk(cv_m);
            i = 1;  // 满足条件
            std::cerr << "Notifying again...\n";
        }
        cv.notify_all();  // 再次唤醒所有线程,然后各个线程依次执行。
    }
     
    int main()
    {
        std::thread t1(waits), t2(waits), t3(waits), t4(signals);
        t1.join(); 
        t2.join(); 
        t3.join();
        t4.join();
    }
    

    输出:

    Waiting... 
    Waiting... 
    Waiting... 
    Notifying...
    Notifying again...
    ...finished waiting. i == 1
    ...finished waiting. i == 1
    ...finished waiting. i == 1
    

    总结:

    调用 wait 之前必须获得锁:

    The converse of this tip, i.e., hold the lock when calling wait, is not just
    a tip, but rather mandated by the semantics of wait, because wait always
    (a) assumes the lock is held when you call it, (b) releases said lock when
    putting the caller to sleep, and © re-acquires the lock just before returning.
    Thus, the generalization of this tip is correct: hold the lock when
    calling signal or wait, and you will always be in good shape.


    c++11 引入了内存模型,除了使用 mutex,还提供了更为高效的 atomic<T> 类型。
    关于内存模型,参见 link, 写的通俗易懂,举例到位。
    关于内存模型,还可以了解一下 c++ 的 type-punning 问题, see link.

    other ref links:
    http://en.cppreference.com/w/cpp/thread/condition_variable/notify_all
    http://www.cplusplus.com/reference/condition_variable/condition_variable/wait/
    http://stackoverflow.com/questions/16350473/why-do-i-need-stdcondition-variable
    http://blog.csdn.net/gw569453350game/article/details/51577319
    http://blog.csdn.net/gw569453350game/article/details/51564925
    http://pages.cs.wisc.edu/~remzi/OSTEP/threads-cv.pdf
    http://stackoverflow.com/questions/32978066/why-is-there-no-wait-function-for-condition-variable-which-does-not-relock-the-m

    展开全文
  • notify_one()与notify_all()常用来唤醒阻塞的线程。 notify_one():因为只唤醒等待队列中的第一个线程;不存在锁争用,所以能够立即获得锁。其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all()。...
  • C++11条件变量之虚假唤醒

    千次阅读 2018-03-07 13:00:02
     对条件变量的使用包括两个动作:在线程开发的过程中,肯定会遇到线程同步,我们会选择C++11中的condition_varible来方便我们处理。当在项目中看到前辈这样写std::unique_lock&lt;std::mutex&gt; lock(m_.....
  • C++条件变量

    2020-09-12 10:17:41
    在多线程编程中有一个条件变量,在头文件中condition_variable,常用在设计线程池的时候使用,该条件变量需要配合mutex才能使用 std::mutex mtx; std::condition_variable cv; void print_id (int id) { std::...
  • C++11多线程之条件变量

    万次阅读 2016-06-04 22:12:07
    原文: ...std::condition_variable 定义在头文件&lt;condition_variable&gt; class condition_variable; (since C++11) condition_variable类是一个同步原语,可以被用来阻...
  • 比如,线程可能需要等待某个条件为真才能继续执行,而一个忙等待循环中可能会导致所有其他线程都无法进入临界区使得条件为真时,就会发生死锁。所以,condition_variable实例被创建出现主要就是用于唤醒等待线程从而...
  • C++11 多线程同步 互斥锁 条件变量

    千次阅读 2017-03-04 21:21:26
    互斥锁和条件变量通常情况下,互斥锁和条件变量是配合使用的,互斥锁用于短期锁定,主要保证线程对临界区的进入;条件变量用于线程长期等待,在wait的时候会释放锁。操作的API如下所示(介绍最常用的): std::m
  • C++并发实战11条件变量

    千次阅读 2013-12-13 12:45:59
    1 线程睡眠函数 std::this_thread::sleep_for(std::chrono::milliseconds(100));//头文件#include,供选择的如seconds()等 不要使用...2 条件变量std::condition_variable不允许拷贝和移动的。其基本语义和Linux的pthr
  • 在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量c++11提供的 condition_variable 类是一个同步原语,它能够阻塞一个或者多个线程,直到...
  • c++11中,条件变量需要头文件: #include 同时,条件变量还需要一个mutex锁 条件变量实际上是如何运作的 ·线程1调用等待条件变量,内部获取mutex互斥锁并检查是否满足条件; ·如果没有,则释放锁,并等待条件...
  • 在C11头文件中包含了如下内容 ...条件变量的主要类,用于实现线程同步。 condition_variable_any 是对condition_variable的扩展,condition_variable只能等待unique_lock。 notify_all_at_thread_exit
  • linux c++之互斥变量和条件变量

    千次阅读 2014-07-04 21:03:07
    /* * main.cpp * * Created on: Jul 3, 2014 * Author: john */ #include #include #include ...//声明互斥锁和条件变量 pthread_mutex_t mutex; pthread_cond_t cond; void* thread1(void* arg
  • 最近在学习多线程的网络编程,互斥量和条件变量是多线程编程中常用的线程同步方式。在编写自己的高并发服务器的过程中对互斥量和条件变量进行了封装,想要测试一下自己封装的类是否正确,能否通过自己封装的条件变量...
  • 互斥量与条件变量 C++11对线程支持有很大的提升(参见C++11线程thread与任务async),可以方便地处理线程。同时提供了互斥量与条件变量,可方便处理类似消费者-生产者问题。 线程 C++11中对线程提供了良好的...
  • 理解条件变量 条件变量可以用来管理thread间的通信。一个线程可以等待在一个条件变量上,直到发生某个事件。 考虑一个场景,一个线程访问一个队列时,...C++11 标准库提供的 condition_variable 概览 conditio...
  • C++11并发编程-条件变量(condition_variable)详解

    万次阅读 多人点赞 2018-08-16 17:28:16
    头文件主要包含了与条件变量相关的类和函数。相关的类包括 std::condition_variable和 std::condition_variable_any,还有枚举类型std::cv_status。另外还包括函数 std::notify_all_at_thread...
  • 目录 写在前面 解析 wait函数 wait_for函数 ...condition_variable条件变量可以阻塞(wait、wait_for、wait_until)调用的线程直到使用(notify_one或notify_all)通知恢复为止 首先要知道condition_v...
  • C++条件变量--std::condition_variable

    万次阅读 2018-07-29 16:59:39
    条件变量允许我们通过通知进而实现线程同步。 因此,您可以实现发送方/接收方或生产者/消费者之类的工作流。 在这样的工作流程中,接收者正在等待发送者的通知。如果接收者收到通知,它将继续工作。 std::...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 195,434
精华内容 78,173
关键字:

c++11条件变量

c++ 订阅