条件变量_条件变量作用 - CSDN
  • Linux 线程同步---条件变量

    万次阅读 多人点赞 2008-03-18 22:17:00
    1. 相关函数 #include pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_init(pt
    1. 相关函数                                                                                         
           #include <pthread.h>
           pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
           int    pthread_cond_init(pthread_cond_t    *cond,    pthread_condattr_t
           *cond_attr);
           int pthread_cond_signal(pthread_cond_t *cond);
           int pthread_cond_broadcast(pthread_cond_t *cond);
           int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
           int   pthread_cond_timedwait(pthread_cond_t   *cond,    pthread_mutex_t
           *mutex, const struct timespec *abstime);
           int pthread_cond_destroy(pthread_cond_t *cond);
                                                                                             
    2. 说明
        条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。
        条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件。
        pthread_cond_init 使用 cond_attr 指定的属性初始化条件变量 cond,当 cond_attr 为 NULL 时,使用缺省的属性。LinuxThreads 实现条件变量不支持属性,因此 cond_attr 参数实际被忽略。
        pthread_cond_t 类型的变量也可以用 PTHREAD_COND_INITIALIZER 常量进行静态初始化。                                                                                         
        pthread_cond_signal 使在条件变量上等待的线程中的一个线程重新开始。如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能重启动,但不能指定哪一个。
        pthread_cond_broadcast 重启动等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。
        pthread_cond_wait 自动解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。在调用 pthread_cond_wait 之前,应用程序必须加锁互斥量。pthread_cond_wait 函数返回前,自动重新对互斥量加锁(如同执行了 pthread_lock_mutex)。
        互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。
        pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在 abstime 指定的时间内 cond 未触发,互斥量 mutex 被重新加锁,且 pthread_cond_timedwait 返回错误 ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime = 0 表示 1970 年 1 月 1 日 00:00:00 GMT。
        pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。在 LinuxThreads 的实现中,条件变量不联结资源,除检查有没有等待的线程外,pthread_cond_destroy 实际上什么也不做。
    3. 取消                                                                                         
        pthread_cond_wait 和 pthread_cond_timedwait 是取消点。如果一个线程在这些函数上挂起时被取消,线程立即继续执行,然后再次对 pthread_cond_wait 和 pthread_cond_timedwait 在 mutex 参数加锁,最后执行取消。因此,当调用清除处理程序时,可确保,mutex 是加锁的。
    4. 异步信号安全(Async-signal Safety)                                                                                         
        条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁。
                                                                                             
    5. 返回值
        在执行成功时,所有条件变量函数都返回 0,错误时返回非零的错误代码。
    6. 错误代码
        pthread_cond_init,   pthread_cond_signal,  pthread_cond_broadcast, 和 pthread_cond_wait 从不返回错误代码。
        pthread_cond_timedwait 函数出错时返回下列错误代码:
            ETIMEDOUT   abstime 指定的时间超时时,条件变量未触发
            EINTR       pthread_cond_timedwait 被触发中断
        pthread_cond_destroy 函数出错时返回下列错误代码:
            EBUSY       某些线程正在等待该条件变量
    7. 举例                                                                                    
        设有两个共享的变量 x 和 y,通过互斥量 mut 保护,当 x > y 时,条件变量 cond 被触发。
                 int x,y;
                 int x,y;
                 pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
                 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
                                                                                             
        等待直到 x > y 的执行流程:
                  pthread_mutex_lock(&mut);
                  while (x <= y) {
                          pthread_cond_wait(&cond, &mut);
                  }
                  /* 对 x、y 进行操作 */
                  pthread_mutex_unlock(&mut);
                                                                                             
         对 x 和 y 的修改可能导致 x > y,应当触发条件变量:                                                                                         
                  pthread_mutex_lock(&mut);
                  /* 修改 x、y */
                  if (x > y) pthread_cond_broadcast(&cond);
                  pthread_mutex_unlock(&mut);
                                                                                             
        如果能够确定最多只有一个等待线程需要被唤醒(例如,如果只有两个线程通过 x、y 通信),则使用 pthread_cond_signal 比 pthread_cond_broadcast 效率稍高一些。如果不能确定,应当用 pthread_cond_broadcast。
        要等待在 5 秒内 x > y,这样处理:
                                                                                             
                  struct timeval now;
                  struct timespec timeout;
                  int retcode;
                                                                                             
                  pthread_mutex_lock(&mut);
                  gettimeofday(&now);
                  timeout.tv_sec = now.tv_sec + 5;
                  timeout.tv_nsec = now.tv_usec * 1000;
                  retcode = 0;
                  while (x <= y && retcode != ETIMEDOUT) {
                          retcode = pthread_cond_timedwait(&cond, &mut, &timeout);
                  }
                  if (retcode == ETIMEDOUT) {
                          /* 发生超时 */
                  } else {
                          /* 操作 x 和  y */
                  }
                  pthread_mutex_unlock(&mut);
    展开全文
  • 条件变量详解

    2019-04-20 20:59:00
    在 上一篇文章结束时,我描述了一个比较特殊的难题:如果线程正在等待某个特定条件发生,它应该如何处理这种情况?它可以重复对互斥对象锁定和解锁,每次都会检查共享数据结构,以查找某个值。但这是在浪费时间和...

    https://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/#1

     

    在 上一篇文章结束时,我描述了一个比较特殊的难题:如果线程正在等待某个特定条件发生,它应该如何处理这种情况?它可以重复对互斥对象锁定和解锁,每次都会检查共享数据结构,以查找某个值。但这是在浪费时间和资源,而且这种繁忙查询的效率非常低。解决这个问题的最佳方法是使用 pthread_cond_wait() 调用来等待特殊条件发生。
    了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。
    首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧是空的。这一特定线程什么也干不了 -- 其设计意图是从列表中除去节点,但是现在却没有节点。因此,它只能:
    锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。
    pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改已链接列表),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。
    此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。
    现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将苏醒。

    现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。

     

     

    http://blog.chinaunix.net/uid-20628302-id-1608577.html

     

    下面说一下,pthread_cond_wait(&mycond,&mymutex)的东西
    1. 设计pthread_cond_wait 的初衷


       线程如果需要等待某个条件发生,它该作何处理呢?它可以重复对互斥对象锁定和解锁,每次都会检查共享元素,以查找某个值。这样会比较浪费cpu的时钟周期。而且效率比较低,何不借鉴一下dma的处理办法,等待某个条件发生了通知一下,这样cpu就可以处理自己的事了,我想pthread_cond_wait的初衷就是这样的,也是这样被设计出来的。如果你有不同解释可以通知我,我们可以共同讨论。


    2. pthread_cond_wait的内部操作
       在调用之前需要锁定互斥对象,然后再调用pthread_cond_wait。
      1> pthread_cond_wait所做的第一件事就是同时对互斥对象解锁(这样其它线程就可以修改共享对象了,操作之前不要忘记加锁哦)。
      2> 等待条件通常是一个阻塞操作(这一点有点不明白,为什么是通常,难道说是还有其它动作,有知道的可以告诉我啊),这意味着线程将睡眠,在它苏醒之前不会消耗cpu周期(这正是我们想要的).线程睡呀睡呀,直到,有人叫它, 比如:另一个线程锁定了mymutex,并对共享对象对了某个动作.在对互斥对象解锁之后(this is very important),2号线程会立即调用函数pthread_cond_broadcast(&mycond)(这个地方也有点不明白, 我想不一定需要立即调用pthread_cond_broadcast,日比如有一个共享的线程数目 pthread_num = 5,当创建一个线程的时候pthread_num++,但是当该线程结束的时候pthread_num--,如果这个数小于5,我们就可以广播一下这个事件,激活一个线程,用以创建更多的线程)。
      3> 调用pthread_cond_wait的线程被叫醒之后,将重新锁定mymutex,之后才返回。


    下面画个图来表示一下期间锁的调用(是伪码)


    lock(mutex)   ----------------a.lock
    pthread_cond_wait()
    {
        unlock(mutex)-------------a.unlock
        if ( 条件不满足)
          睡觉
        else 
        {
          lock(mutex)-------------b.lock /条件成立后 内部锁定了 mutex 和后面的 b.unlock 对应
          return
        }
    }


    dosomething();


    unlock(mutex);---------------b.unlock


    这样就可以看出来锁的调用了吧。

    展开全文
  • 一、何为条件变量 在前一篇文章《C++多线程并发编程(二)—线程同步之互斥锁》中解释了线程同步的原理和实现,使用互斥锁解决数据竞争访问问题,算是线程同步的加锁原语,用于排他性的访问共享数据。我们在使用...

    一、何为条件变量

    在前一篇文章《C++多线程并发(二)—线程同步之互斥锁》中解释了线程同步的原理和实现,使用互斥锁解决数据竞争访问问题,算是线程同步的加锁原语,用于排他性的访问共享数据。我们在使用mutex时,一般都会期望加锁不要阻塞,总是能立刻拿到锁,然后尽快访问数据,用完之后尽快解锁,这样才能不影响并发性和性能。

    如果需要等待某个条件的成立,我们就该使用条件变量(condition variable)了,那什么是条件变量呢,引用APUE中的一句话:

    Condition variables are another synchronization mechanism available to threads.
    These synchronization objects provide a place for threads to rendezvous. When used with mutexes, condition variables allow threads to wait in a race-free way for arbitrary conditions to occur.

    条件变量是线程的另外一种有效同步机制。这些同步对象为线程提供了交互的场所(一个线程给另外的一个或者多个线程发送消息),我们指定在条件变量这个地方发生,一个线程用于修改这个变量使其满足其它线程继续往下执行的条件,其它线程则等待接收条件已经发生改变的信号。当条件变量同互斥锁一起使用时,条件变量允许线程以一种无竞争的方式等待任意条件的发生。

    二、为何引入条件变量

    前一章介绍了多线程并发访问共享数据时遇到的数据竞争问题,我们通过互斥锁保护共享数据,保证多线程对共享数据的访问同步有序。但如果一个线程需要等待一个互斥锁的释放,该线程通常需要轮询该互斥锁是否已被释放,我们也很难找到适当的轮训周期,如果轮询周期太短则太浪费CPU资源,如果轮询周期太长则可能互斥锁已被释放而该线程还在睡眠导致发生延误。

    下面给出一个简单的程序示例:一个线程往队列中放入数据,一个线程从队列中提取数据,取数据前需要判断一下队列中确实有数据,由于这个队列是线程间共享的,所以,需要使用互斥锁进行保护,一个线程在往队列添加数据的时候,另一个线程不能取,反之亦然。程序实现代码如下:

    //cond_var1.cpp用互斥锁实现一个生产者消费者模型
    
    #include <iostream>
    #include <deque>
    #include <thread>
    #include <mutex>
    
    std::deque<int> q;						//双端队列标准容器全局变量
    std::mutex mu;							//互斥锁全局变量
    //生产者,往队列放入数据
    void function_1() {
        int count = 10;
        while (count > 0) {
            std::unique_lock<std::mutex> locker(mu);
            q.push_front(count);			//数据入队锁保护
            locker.unlock();
            std::this_thread::sleep_for(std::chrono::seconds(1));		//延时1秒
            count--;
        }
    }
    //消费者,从队列提取数据
    void function_2() {
        int data = 0;
        while ( data != 1) {
            std::unique_lock<std::mutex> locker(mu);
            if (!q.empty()) {			//判断队列是否为空
                data = q.back();
                q.pop_back();			//数据出队锁保护
                locker.unlock();
                std::cout << "t2 got a value from t1: " << data << std::endl;
            } else {
                locker.unlock();
            }
        }
    }
    
    int main() {
        std::thread t1(function_1);
        std::thread t2(function_2);
        t1.join();
        t2.join();
    
        getchar();
        return 0;
    }
    

    程序执行结果如下:
    cond_var1执行结果
    从代码中不难看出:在生产过程中,因每放入一个数据有1秒延时,所以这个生产的过程是很慢的;在消费过程中,存在着一个while循环,只有在接收到表示结束的数据的时候,才会停止,每次循环内部,都是先加锁,判断队列不空,然后就取出一个数,最后解锁。所以说,在1s内,做了很多无用功!这样的话,CPU占用率会很高,可能达到100%(单核)。如下图示:
    cond_var1资源消耗
    既然是由于消费者在while循环内因等待数据做了过多的无用功导致CPU占有率过高,我们可以考虑在消费者发现队列为空时,让消费者小睡一会儿,即增加一个小延时(比如500ms),相当于增大了轮询间隔周期,应该能降低CPU的占用率。按该方案修改后的消费者代码如下:

    //消费者,从队列提取数据
    void function_2() {
        int data = 0;
        while ( data != 1) {
            std::unique_lock<std::mutex> locker(mu);
            if (!q.empty()) {			//判断队列是否为空
                data = q.back();
                q.pop_back();			//数据出队锁保护
                locker.unlock();
                std::cout << "t2 got a value from t1: " << data << std::endl;
            } else {
                locker.unlock();
                std::this_thread::sleep_for(std::chrono::milliseconds(500));		//延时500毫秒
            }
        }
    }
    

    增大轮询周期后,CPU占有率下降很明显:
    cond_var1改进后的资源消耗
    但前面也说了,困难之处在于如何确定这个延长时间(即轮询间隔周期),如果间隔太短会过多占用CPU资源,如果间隔太长会因无法及时响应造成延误。

    这就引入了条件变量来解决该问题:条件变量使用“通知—唤醒”模型,生产者生产出一个数据后通知消费者使用,消费者在未接到通知前处于休眠状态节约CPU资源;当消费者收到通知后,赶紧从休眠状态被唤醒来处理数据,使用了事件驱动模型,在保证不误事儿的情况下尽可能减少无用功降低对资源的消耗。

    三、如何使用条件变量

    C++标准库在< condition_variable >中提供了条件变量,借由它,一个线程可以唤醒一个或多个其他等待中的线程。原则上,条件变量的运作如下:

    • 你必须同时包含< mutex >和< condition_variable >,并声明一个mutex和一个condition_variable变量;
    • 那个通知“条件已满足”的线程(或多个线程之一)必须调用notify_one()或notify_all(),以便条件满足时唤醒处于等待中的一个条件变量;
    • 那个等待"条件被满足"的线程必须调用wait(),可以让线程在条件未被满足时陷入休眠状态,当接收到通知时被唤醒去处理相应的任务;

    将上面的cond_var1.cpp程序使用条件变量解决轮询间隔难题的示例代码如下:

    //cond_var2.cpp用条件变量解决轮询间隔难题
    
    #include <iostream>
    #include <deque>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    
    std::deque<int> q;						//双端队列标准容器全局变量
    std::mutex mu;							//互斥锁全局变量
    std::condition_variable cond;           //全局条件变量
    //生产者,往队列放入数据
    void function_1() {
        int count = 10;
        while (count > 0) {
            std::unique_lock<std::mutex> locker(mu);
            q.push_front(count);			//数据入队锁保护
            locker.unlock();
            
            cond.notify_one();              // 向一个等待线程发出“条件已满足”的通知
            
            std::this_thread::sleep_for(std::chrono::seconds(1));		//延时1秒
            count--;
        }
    }
    //消费者,从队列提取数据
    void function_2() {
        int data = 0;
        while ( data != 1) {
            std::unique_lock<std::mutex> locker(mu);
            
            while(q.empty())        //判断队列是否为空
                cond.wait(locker); // 解锁互斥量并陷入休眠以等待通知被唤醒,被唤醒后加锁以保护共享数据
    
            data = q.back();
            q.pop_back();			//数据出队锁保护
            locker.unlock();
            std::cout << "t2 got a value from t1: " << data << std::endl;
        }
    }
    
    int main() {
        std::thread t1(function_1);
        std::thread t2(function_2);
        t1.join();
        t2.join();
    
        getchar();
        return 0;
    }
    

    使用条件变量对CPU的占用率也很低,而且免去了轮询间隔该设多长的难题:
    cond_var2资源消耗
    上面的代码有三个注意事项:

    1. 在function_2中,在判断队列是否为空的时候,使用的是while(q.empty()),而不是if(q.empty()),这是因为wait()从阻塞到返回,不一定就是由于notify_one()函数造成的,还有可能由于系统的不确定原因唤醒(可能和条件变量的实现机制有关),这个的时机和频率都是不确定的,被称作伪唤醒。如果在错误的时候被唤醒了,执行后面的语句就会错误,所以需要再次判断队列是否为空,如果还是为空,就继续wait()阻塞;
    2. 在管理互斥锁的时候,使用的是std::unique_lock而不是std::lock_guard,而且事实上也不能使用std::lock_guard。这需要先解释下wait()函数所做的事情,可以看到,在wait()函数之前,使用互斥锁保护了,如果wait的时候什么都没做,岂不是一直持有互斥锁?那生产者也会一直卡住,不能够将数据放入队列中了。所以,wait()函数会先调用互斥锁的unlock()函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作。lock_guard没有lock和unlock接口,而unique_lock提供了,这就是必须使用unique_lock的原因;
    3. 使用细粒度锁,尽量减小锁的范围,在notify_one()的时候,不需要处于互斥锁的保护范围内,所以在唤醒条件变量之前可以将锁unlock()。

    还可以将cond.wait(locker)换一种写法,wait()的第二个参数可以传入一个函数表示检查条件,这里使用lambda函数最为简单,如果这个函数返回的是true,wait()函数不会阻塞会直接返回,如果这个函数返回的是false,wait()函数就会阻塞着等待唤醒,如果被伪唤醒,会继续判断函数返回值。代码示例如下:

    //消费者,从队列提取数据
    void function_2() {
        int data = 0;
        while ( data != 1) {
            std::unique_lock<std::mutex> locker(mu);
            
            cond.wait(locker, [](){ return !q.empty();});   //如果条件变量被唤醒,检查队列非空条件是否为真,为真则直接返回,为假则继续等待
            
            data = q.back();
            q.pop_back();			//数据出队锁保护
            locker.unlock();
            std::cout << "t2 got a value from t1: " << data << std::endl;
        }
    }
    

    下面给出条件变量支持的操作函数表:
    条件变量操作函数
    值得注意的是:

    • 所有通知(notification)都会被自动同步化,所以并发调用notify_one()和notify_all()不会带来麻烦;
    • 所有等待某个条件变量(condition variable)的线程都必须使用相同的mutex,当wait()家族的某个成员被调用时该mutex必须被unique_lock锁定,否则会发生不明确的行为;
    • wait()函数会执行“解锁互斥量–>陷入休眠等待–>被通知唤醒–>再次锁定互斥量–>检查条件判断式是否为真”几个步骤,这意味着传给wait函数的判断式总是在锁定情况下被调用的,可以安全的处理受互斥量保护的对象;但在"解锁互斥量–>陷入休眠等待"过程之间产生的通知(notification)会被遗失。

    线程同步保证了多个线程对共享数据的有序访问,目前我们了解到的多线程间传递数据主要是通过共享数据(全局变量)实现的,全局共享变量的使用容易增加不同任务或线程间的耦合度,也增加了引入bug的风险,所以全局共享变量应尽可能少用。很多时候我们只需要传递某个线程或任务的执行结果,以便参与后续的运算,但我们又不想阻塞等待该线程或任务执行完毕,而是继续执行暂时不需要该线程或任务执行结果参与的运算,当需要该线程执行结果时直接获得,才能更充分发挥多线程并发的效率优势。想了解该问题,请继续阅读下一篇文章:《C++多线程并发(四)—异步编程》

    更多文章:

    展开全文
  • C++11条件变量使用详解

    万次阅读 热门讨论 2020-07-20 21:52:13
    在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;
    }
    

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

    展开全文
  • 条件变量

    千次阅读 热门讨论 2018-09-04 14:20:18
    1、条件变量概述:   条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量...
  • Linux下条件变量详解

    2019-08-17 20:39:06
    条件变量可以让线程在满足特定的条件下暂停(睡眠),需要与互斥量配合使用。 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 通过宏对条件变量初始化 int pthread_cond_init (pthread_cond_t cond,pthread_...
  • 当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量 假设没有条件变量,对于一个生产者消费者问题,消费线程在得知队列中没有...
  • 条件变量(condition variable)详解

    千次阅读 2016-11-04 13:42:37
    原理: 假设我们需要解决这样一个问题:一个列表记录需要处理的任务。一个线程往此列表添加任务,一个线程processTask处理此列表中的任务。这个问题的一个关键点在于processTask怎么判断任务列表不为空。...
  • 条件变量和信号量

    千次阅读 2019-08-08 14:57:30
    1、条件变量 条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程使的条件成立,并通知等待的线程继续。为了防止...
  • Windows下条件变量的实现

    千次阅读 热门讨论 2018-06-11 16:29:07
    条件变量是什么? 是一种同步对象。 条件变量有什么用?用于复杂的、多线程的、多核的程序中,实现多个线程间同步任务。 条件变量与其它同步对象的区别?与事件、互斥锁、segment等同步对象相比,条件变量最大的...
  • 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。...一旦其他的某个线程改变了条件变量,会发出一个signal通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新
  • 最近在阅读《现代操作系统》,看到条件变量部分时非常疑惑条件变量为什么需要互斥锁,书上也没有非常明显解说原因(其实有说,但是我看书不仔细),查阅了一些资料,进行了一些思考。  我的疑惑点在于条件变量为...
  • linux互斥锁和条件变量的关系

    千次阅读 2017-02-26 02:18:44
    互斥锁一个明显的缺点是它只有两...一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进
  • 多线程 条件变量

    千次阅读 2016-08-05 14:59:22
    条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程使的条件成立,并通知等待的线程继续。为了防止竞争,条件变量...
  • Windows下条件变量实现读写同步

    千次阅读 2016-07-17 21:23:45
    上一篇写了个小程序,运用了下linux下条件变量实现多线程的同步,在windows上也有条件变量这个概念,只不过它是到vista之后才有支持,在linux上条件变量需要结合互斥量一起使用,Windows上的条件变量可以结合临界区...
  • 互斥锁和条件变量使用实例

    千次阅读 2017-05-22 22:12:59
    条件变量简介: 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样。 用法 条件变量使我们可以睡眠等待某种条件出现。 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一...
  • 对C++11条件变量的理解(逻辑)

    千次阅读 2017-02-17 22:53:05
    之前对条件变量一直抱有疑惑,通过互斥锁已经能够保证同一时刻只有一个线程访问共享区资源了,那还要用条件变量干什么呢?说到这里,解惑来源于这篇文章c++11线程之条件变量condition_variable,互斥锁只能保证线程...
  • C++11 多线程同步 互斥锁 条件变量

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

    千次阅读 2018-11-02 21:39:39
    文章目录条件变量之虚假唤醒引言1.什么是虚假唤醒?2.什么情况下会发生虚假唤醒3.如何避免虚假唤醒 条件变量之虚假唤醒 引言 当我们使用互斥量(Mutex)与条件变量(condition_variable)进行多线程同步时有可能会...
  • Linux多线程之条件变量

    千次阅读 2018-03-19 16:26:54
    上一节中,Linux多线程之互斥锁最后遗留了一个问题...互斥锁用于上锁,条件变量则用于等待。这两种不同类型的同步都是需要的。条件变量是类型为pthread_cond_t的变量,以下两个函数使用了这些#include &lt;pthre...
1 2 3 4 5 ... 20
收藏数 1,030,352
精华内容 412,140
关键字:

条件变量