精华内容
下载资源
问答
  • 条件变量

    千次阅读 多人点赞 2018-09-04 11:26:14
    1、条件变量概述:   条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量...

    1、条件变量概述: 

          条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用

           当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待

    2、主要应用函数:

    pthread_cond_init()函数               功能:初始化一个条件变量

    pthread_cond_wait()函数             功能:阻塞等待一个条件变量

    pthread_cond_timedwait()函数    功能:限时等待一个条件变量

    pthread_cond_signal()函数          功能:唤醒至少一个阻塞在条件变量上的线程

    pthread_cond_broadcast()函数    功能:唤醒全部阻塞在条件变量上的线程

    pthread_cond_destroy()函数        功能:销毁一个条件变量

    以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

    pthread_cond_t 类型,其本质是一个结构体为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:

    pthread_cond_t  cond; 变量cond只有两种取值1、0。

    3、函数分析  

    <1>、初始化一个条件变量

    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 

    参2:attr表条件变量属性,通常为默认值,传NULL即可

    也可以使用静态初始化的方法,初始化条件变量:

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

     <2>、阻塞等待一个条件变量

     int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 

    函数作用:

    1. 阻塞等待条件变量cond(参1)满足
    2. 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);

     1.2.两步为一个原子操作。

         3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

    <3>、限时等待一个条件变量 

    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 

    参3: 参看man sem_timedwait函数,查看struct timespec结构体。

    struct timespec {

    time_t tv_sec; /* seconds */ 秒

    long   tv_nsec; /* nanosecondes*/ 纳秒

    }

    形参abstime:绝对时间。

    如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。

    struct timespec t = {1, 0};

    pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去) 

    正确用法:

    time_t cur = time(NULL); 获取当前时间。

    struct timespec t; 定义timespec 结构体变量t

    t.tv_sec = cur+1; 定时1秒

    pthread_cond_timedwait (&cond, &mutex, &t); 传参 参APUE.11.6线程同步条件变量小节

    <4>、唤醒至少一个阻塞在条件变量上的线程  

    int pthread_cond_signal(pthread_cond_t *cond); 

    <5>、唤醒全部阻塞在条件变量上的线程 

     int pthread_cond_broadcast(pthread_cond_t *cond); 

    <6>、销毁一个条件变量 

    int pthread_cond_destroy(pthread_cond_t *cond); 

    4、条件变量示例 

    生产者消费者条件变量模型

           线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    //节点结构体
    struct msg
    {
        int num; //数据区
        struct msg *next; //链表区
    };
    
    struct msg *head = NULL;//头指针
    struct msg *mp = NULL;  //节点指针
    //利用宏定义的方式初始化全局的互斥锁和条件变量
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
    
    void *producter(void *arg)
    {
        while (1) 
    	{
            mp = malloc(sizeof(struct msg));
            mp->num = rand() % 400 + 1;
            printf("---producted---%d\n", mp->num);
    
            pthread_mutex_lock(&mutex);//访问共享区域必须加锁
            mp->next = head;
            head = mp;
            pthread_mutex_unlock(&mutex);
    
            pthread_cond_signal(&has_product);//通知消费者来消费
    		
            sleep(rand() % 3);
        }
    
        return NULL;
    }
    
    void *consumer(void *arg)
    {
        while (1)
    	{
            pthread_mutex_lock(&mutex);//访问共享区域必须加锁
            while (head == NULL)//如果共享区域没有数据,则解锁并等待条件变量
    	{
                pthread_cond_wait(&has_product, &mutex);
            }
            mp = head;
            head = mp->next;
            pthread_mutex_unlock(&mutex);
    
            printf("------------------consumer--%d\n", mp->num);
            free(mp); //释放被删除的节点内存
            mp = NULL;//并将删除的节点指针指向NULL,防止野指针
    		
            sleep(rand() % 3);
        }
    
        return NULL;
    }
    
    int main(void)
    {
        pthread_t ptid, ctid;
    
        //创建生产者和消费者线程
        pthread_create(&ptid, NULL, producter, NULL);
        pthread_create(&ctid, NULL, consumer, NULL);
        //主线程回收两个子线程
        pthread_join(ptid, NULL);
        pthread_join(ctid, NULL);
    
        return 0;
    }
    

    5、运行结果 

     

    6、条件变量的优点:

    相较于mutex而言,条件变量可以减少竞争。

    如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

     

     

    展开全文
  • 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;
    }
    

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

    展开全文
  • pthread 条件变量

    千次阅读 2016-07-05 23:24:45
    pthread条件变量

    pthread条件变量

    muduo/base/Condition.h中有对条件变量的封装,底层是pthread 条件变量。

    条件变量

    条件变量是线程使用的一种同步机制。条件变量给多个线程提供了会合的场所。条件变量和互斥量一起用的时候,允许线程以无竞争的方式等待特定的条件发生。条件是受互斥量保护的。线程在改变条件状态时必须首先对互斥量加锁。

    相关函数

    1.初始化与销毁

    pthread_cond_t数据类型表示条件变量,使用以下两个函数对条件变量进行初始化和销毁:

    #include <pthread.h>
    int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);

    注:关键字restrict只用于限定指针;该关键字用于告知编译器,所有修改该指针所指向内容的操作全部都是基于(base on)该指针的,即不存在其它进行修改操作的途径;这样的后果是帮助编译器进行更好的代码优化,生成更有效率的汇编代码。

    2.等待条件变量

    我们使用pthread_cond_wait等待条件变量变为真:

    #include <pthread.h>
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

    当线程调用pthread_cond_wait()函数时,会将调用线程放到等待条件的线程列表上,并原子的对互斥量解锁(这样就不会死锁)。当pthread_cond_wait()返回时,互斥量再次锁住。

    3.通知线程条件满足

    我们使用两个函数可以通知线程条件已经满足。pthread_cond_signal函数至少能唤醒一个等待该条件的线程。pthread_cond_broadcast函数能唤醒等待该条件的所有线程。

    #include <pthread.h>
    int pthread_cond_signal(pthread_cond_t * cond);
    int pthread_cond_broadcast(pthread_cond_t * cond);

    这两个函数都可以给条件等待线程发信号,不过需要注意的是,一定要在改变条件状态以后再给线程发信号。

    示例

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #define NUM_THREADS  3
    #define TCOUNT 10
    #define COUNT_LIMIT 12
    
    int     count = 0;
    pthread_mutex_t count_mutex;
    pthread_cond_t count_threshold_cv;
    
    void *inc_count(void *t) //增加count
    {
        int i;
        long my_id = (long)t;
    
        for (i=0; i < TCOUNT; i++)
        {
            pthread_mutex_lock(&count_mutex);
            count++;
    
            if (count == COUNT_LIMIT) //满足条件后
            {
                printf("inc_count(): thread %ld, count = %d  Threshold reached. ",my_id, count);
                pthread_cond_signal(&count_threshold_cv);//通知
                printf("Just sent signal.\n");
            }
            printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",my_id, count);
    
            //这里释放锁的同时 sleep 1 秒中可以保证线程2和线程3交替获得锁并执行
            pthread_mutex_unlock(&count_mutex);
            sleep(1);
        }//end for
        pthread_exit(NULL);
    }
    
    void *watch_count(void *t) //检查条件变量
    {
        long my_id = (long)t;
    
        printf("Starting watch_count(): thread %ld\n", my_id);
    
        pthread_mutex_lock(&count_mutex);
        while (count < COUNT_LIMIT)//这里用while防止虚假唤醒
        {
            printf("watch_count(): thread %ld Count= %d. Going into wait...\n", my_id,count);
            pthread_cond_wait(&count_threshold_cv, &count_mutex);//阻塞后自动释放锁
            printf("watch_count(): thread %ld Condition signal received. Count= %d\n", my_id,count);
            printf("watch_count(): thread %ld Updating the value of count...\n", my_id,count);
            count += 125;
            printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
        }
        printf("watch_count(): thread %ld Unlocking mutex.\n", my_id);
        pthread_mutex_unlock(&count_mutex);
        pthread_exit(NULL);
    }
    
    int main(int argc, char *argv[])
    {
        int i, rc; 
        long t1=1, t2=2, t3=3;
        pthread_t threads[3];//3个线程
        pthread_attr_t attr;//attr
    
        /* 初始化 mutex 和 condition variable  */
        pthread_mutex_init(&count_mutex, NULL); 
        pthread_cond_init (&count_threshold_cv, NULL);
    
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
        pthread_create(&threads[0], &attr, watch_count, (void *)t1);//线程1关注count值
        pthread_create(&threads[1], &attr, inc_count, (void *)t2);//线程2增加count值
        pthread_create(&threads[2], &attr, inc_count, (void *)t3);//线程3增加count值
    
        for (i = 0; i < NUM_THREADS; i++) 
        {
            pthread_join(threads[i], NULL);
        }//等待所有线程完成
        printf ("Main(): Waited and joined with %d threads. Final value of count = %d. Done.\n", NUM_THREADS, count);
    
        /* Clean up and exit */
        pthread_attr_destroy(&attr);
        pthread_mutex_destroy(&count_mutex);
        pthread_cond_destroy(&count_threshold_cv);
        pthread_exit (NULL);
    
    }
    
    展开全文
  • 条件变量和信号量

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

    1、条件变量

    条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程使的条件成立,并通知等待的线程继续。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

    2、windows条件变量的实现

    (1)利用同步对象实现条件变量

    自己封装的一个条件变量:

     1 #ifndef _MY_CONDITION_H
     2 #define _MY_CONDITION_H
     3 
     4 #include <windows.h>
     5 
     6 class MyCondition
     7 {
     8 public:
     9     MyCondition()
    10     {
    11         m_hEvent = CreateEvent(NULL,TRUE,FALSE, NULL);
    12     }
    13 
    14     ~MyCondition()
    15     {
    16         ::CloseHandle(m_hEvent);
    17     }
    18 
    19     void ResetSignal()
    20     {
    21         ResetEvent(m_hEvent);
    22     }
    23 
    24     void Active()
    25     {
    26         ::SetEvent(m_hEvent);
    27     }
    28 
    29     bool timed_wait(int nSecond)
    30     {
    31         bool bRet=true;
    32         if(INFINITE == nSecond)
    33         {
    34             if (WAIT_OBJECT_0 != ::WaitForSingleObject(m_hEvent, INFINITE))
    35                 bRet=false;
    36         }
    37         else
    38         {
    39             if (WAIT_OBJECT_0 != ::WaitForSingleObject(m_hEvent, nSecond*1000))
    40                 bRet=false;
    41         }
    42 
    43         ResetEvent(m_hEvent);
    44         return bRet;
    45     }
    46 
    47 private:
    48     HANDLE m_hEvent;
    49 };
    50 
    51 #endif

    CreateEvent时,第二个参数设置手动和自动两种模式,这两种模式将影响SetEvent、ResetEvent和PulseEvent操作,分别如下:

     

     

    自动模式

    手动模式

    SetEvent

    将Event调为激发态,放过一个等待线程,而后自动调回非激发态

    一直放过等待

    将Event调为激发态

    ResetEvent

    无用

    停止放过线程

    将Event调为非激发态

    PulseEvent

    这种模式下等同与SetEvent

    将Event调为激发态,放过所有的等待的线程,然后调回非激发态

    (2)CONDITION_VARIABLE

    微软从vista和2008以后引入的技术,xp和2003的系统不支持,相关的操作函数如下:

    l  WakeConditionVariable           唤醒一个等待条件变量的线程

    l  WakeAllConditionVariable      唤醒所有等待条件变量的线程;

    l  SleepConditionVariableCS       释放临界区锁和等待条件变量作为原子性操作

    l  SleepConditionVariableSRW   释放SRW锁和等待条件变量作为原子性操作。

    3、linux条件变量的实现

    自己封装的一个条件变量:

     1 #ifndef _MY_CONDITION_H
     2 #define _MY_CONDITION_H
     3 
     4 #include <pthread.h>
     5 #include <time.h>
     6 #define INFINITE            0xFFFFFFFF
     7 
     8 
     9 class MyCondition
    10 {
    11 public:
    12     MyCondition()
    13     {
    14         pthread_condattr_t cond;
    15         pthread_condattr_init(&cond);
    16         pthread_condattr_setclock(&cond, CLOCK_MONOTONIC);
    17         pthread_cond_init(&m_pthCondt, &cond);
    18         pthread_mutex_init(&m_mtxCondt, NULL);
    19     }
    20 
    21     ~MyCondition()
    22     {
    23         pthread_cond_destroy(&m_pthCondt);
    24         pthread_mutex_destroy(&m_mtxCondt);
    25     }
    26 
    27     void ResetSignal()
    28     {
    29     }
    30 
    31     void Active()
    32     {
    33         pthread_mutex_lock(&m_mtxCondt);
    34         pthread_cond_broadcast(&m_pthCondt);
    35         pthread_mutex_unlock(&m_mtxCondt);
    36     }
    37 
    38     bool timed_wait(int nSecond)
    39     {
    40         bool bRet = true;
    41         if (INFINITE == nSecond)
    42         {
    43             pthread_mutex_lock(&m_mtxCondt);
    44             if (0 != pthread_cond_wait(&m_pthCondt, &m_mtxCondt))
    45                 bRet = false;
    46             pthread_mutex_unlock(&m_mtxCondt);
    47         }
    48         else
    49         {
    50             pthread_mutex_lock(&m_mtxCondt);
    51             struct timespec tv;
    52             clock_gettime(CLOCK_MONOTONIC, &tv);
    53             tv.tv_sec += nSecond;
    54             if (0 != pthread_cond_timedwait(&m_pthCondt, &m_mtxCondt, &tv))
    55                 bRet = false;
    56             pthread_mutex_unlock(&m_mtxCondt);
    57         }
    58 
    59         return bRet;
    60     }
    61 
    62 private:
    63     pthread_cond_t    m_pthCondt;
    64     pthread_mutex_t   m_mtxCondt;
    65 };
    66 
    67 #endif

    Linux提供了的条件等待函数和notify函数。

    l  pthread_cond_timedwait(cond, mutex, abstime);

    l  pthread_cond_wait(cond, mutex);

    l  pthread_cond_signal(cond);    将至少解锁一个线程(阻塞在条件变量上的线程)。

    l  pthread_cond_broadcast(cond) : 将对所有阻塞在条件变量上的线程解锁。

    pthread_cond_wait() 所做的事包含三个部分:

      1)同时对mutex解锁

      2)并等待条件 cond 发生

      3)获得通知后,对mutex加锁

    4、虚假唤醒

    唤醒操作(SetEvent和pthread_cond_signal)原本意图是唤醒一个等待的线程,但是在多核处理器下,可能会激活多个等待的线程,这种效应为“虚假唤醒”。linux帮助文档中提到:虽然虚假唤醒在pthread_cond_wait函数中可以解决,为了发生概率很低的情况而降低边缘条件(fringe condition)效率是不值得的,纠正这个问题会降低对所有基于它的所有更高级的同步操作的并发度。所以pthread_cond_wait的实现上没有去解决它。所以通常的解决办法是在线程被激活后还需要检测等待的条件是否满足,例如下图所示。

    pthread_cond_wait中的while()不仅仅在等待条件变量前检查条件,实际上在等待条件变量后也检查条件。

    5、唤醒丢失

     如果在等待条件变量(pthread_cond_wait)前,条件变量就被唤醒激活(pthread_cond_signal),那么这次唤醒就会丢失。

    例如客户端向服务端发送同步消息时,客户端需要等到服务的回应再返回发送接口,这时需要在发送接口内部等待回应。

    利用条件变量实现如下:

     1 //ClientSession.h
     2 #ifndef _CLIENT_SESSION_H_
     3 #define _CLIENT_SESSION_H_
     4 
     5 class ClientSession
     6 {
     7 public:
     8     ClientSession();
     9     ~ClientSession();
    10 
    11 public:
    12     void OnRecvServerResponse();
    13 
    14 public:
    15     int SendSyncMsg(string sMsg);
    16 
    17 private:
    18     MyCondition m_objCond;
    19     TcpServer m_objTcpServer;
    20 };
    21 
    22 #endif
     1 //ClientSession.cpp
     2 
     3 #include "ClientSession.h"
     4 
     5 ClientSession::ClientSession()
     6 {
     7 }
     8 
     9 ClientSession::~ClientSession()
    10 {
    11 }
    12 
    13 void ClientSession::OnRecvServerResponse()
    14 {
    15     m_objCond.Active();                        //唤醒等待
    16 }
    17 
    18 int SendSyncMsg(string sMsg)
    19 {
    20     long lRet = m_objTcpServer.SendData(sMsg.c_str(),sMsg.length());
    21     if (lRet >= 0)                            //网络发送成功
    22     {
    23         if (m_objCond.time_wait(5000) >= 0)    //收到服务端回应
    24         {
    25             return 0;                        //发送成功
    26         }
    27         else                                //服务端回应超时
    28         {
    29             return -1;
    30         }
    31     }
    32     return -1;
    33 }

    SendSyncMsg中存在问题,如果在SendData之后,m_objCond.time_wait(5000)之前,m_objCond.Active()被调用,则会出现唤醒丢失。

    唤醒丢失问题可以采用信号量来解决。

    6、C++11中条件变量

    从C++11之后,c++标准库实现了条件变量,具体可以参考http://www.cplusplus.com/reference/condition_variable/condition_variable/?kw=condition_variable

    7、信号量

    信号量包含一个信号值,在windows和linux中实现如下:

      1 #ifndef _TIME_SEM_H_
      2 #define _TIME_SEM_H_
      3 
      4 #include <iostream>
      5 #include <assert.h>
      6 using namespace std;
      7 
      8 #ifdef _MSC_VER
      9 #define WIN32_LEAN_AND_MEAN        // 从 Windows 头中排除极少使用的资料
     10 
     11 #  if !defined(_WINDOWS_)
     12 #    include <windows.h>
     13 #    include <winbase.h>
     14 #  endif
     15 
     16 #elif defined(__GNUC__)
     17 #  include <pthread.h>
     18 #  include <unistd.h>
     19 #  include <sys/time.h>
     20 #  include <errno.h>
     21 #  ifndef __bsdi__
     22 #    include <semaphore.h>
     23 #  endif
     24 #endif
     25 
     26 
     27 
     28 #ifdef _MSC_VER
     29 
     30 class TimeSem
     31 {
     32 public:
     33     TimeSem(int intial_count = 0)
     34     {
     35         m_sem = CreateSemaphore(NULL, intial_count, 65535, NULL);
     36     }
     37     ~TimeSem() { CloseHandle(m_sem); }
     38     bool Wait(int timeout = INFINITE)
     39     {
     40         return WaitForSingleObject(m_sem, timeout) == WAIT_OBJECT_0;
     41     }
     42     bool TryWait() { return Wait(0); }
     43     void Signal() { ReleaseSemaphore(m_sem, 1, NULL); }
     44 
     45 protected:
     46     HANDLE m_sem;
     47 };
     48 
     49 #elif defined(__GNUC__)
     50 
     51 class TimeSem
     52 {
     53 public:
     54     TimeSem(int intial_count = 0) : m_count(intial_count)
     55     {
     56         pthread_cond_init(&m_cond, NULL);
     57         pthread_mutex_init(&m_mtx, NULL);
     58         pipe(m_fds);
     59         if (m_count > 0)
     60         {
     61             for (int i = 0; i < intial_count; i++)
     62             {
     63                 char buf[] = "semaphore: signal";
     64                 write(m_fds[1], buf, sizeof(buf));
     65             }
     66         }
     67     }
     68     ~TimeSem()
     69     {
     70         pthread_cond_destroy(&m_cond);
     71         pthread_mutex_destroy(&m_mtx);
     72         close(m_fds[0]);  close(m_fds[1]);
     73     }
     74     bool Wait(int timeout = -1)
     75     {
     76         //pthread_mutex_lock(&m_mtx);
     77 
     78         fd_set rset;
     79         FD_ZERO(&rset);
     80         FD_SET(m_fds[0], &rset);
     81 
     82         int ret = -1;
     83         if (timeout < 0)
     84             ret = select(m_fds[0] + 1, &rset, NULL, NULL, NULL);
     85         else
     86         {
     87             timeval tv;
     88 
     89             tv.tv_sec = timeout / 1000;
     90             tv.tv_usec = (timeout % 1000) * 1000;
     91 
     92             ret = select(m_fds[0] + 1, &rset, NULL, NULL, &tv);
     93         }
     94 
     95         if (ret == 1 && FD_ISSET(m_fds[0], &rset))
     96         {
     97             if (--m_count >= 0);
     98             {
     99                 char buf[] = "semaphore: signal";
    100                 read(m_fds[0], buf, sizeof(buf));
    101             }
    102         }
    103         else
    104         {
    105             //pthread_mutex_unlock(&m_mtx);            
    106             return false;
    107         }
    108 
    109         //pthread_mutex_unlock(&m_mtx);
    110         return true;
    111     }
    112     bool TryWait()
    113     {
    114         bool res = false;
    115         pthread_mutex_lock(&m_mtx);
    116         res = m_count > 0;
    117         if (m_count > 0)
    118         {
    119             char buf[] = "semaphore: signal";
    120             read(m_fds[0], buf, sizeof(buf));
    121             --m_count;
    122         }
    123         pthread_mutex_unlock(&m_mtx);
    124         return res;
    125     }
    126     void release()
    127     {
    128         //pthread_mutex_lock(&m_mtx);
    129         if (++m_count > 0)
    130         {
    131             char buf[] = "semaphore: signal";
    132             write(m_fds[1], buf, sizeof(buf));
    133         }
    134         //pthread_mutex_unlock(&m_mtx);
    135     }
    136     void signal() { release(); }
    137 
    138 protected:
    139     pthread_cond_t        m_cond;
    140     pthread_mutex_t        m_mtx;
    141     int                    m_count;
    142     int     m_fds[2];
    143 };
    144 
    145 #endif
    146 
    147 #endif

    8、信号量和条件变量的区别和联系

    (1)使用条件变量可以一次唤醒所有等待者,而这个信号量没有的功能,感觉是最大区别。

    (2)信号量是有一个值(状态的),而条件变量是没有的,没有地方记录唤醒(发送信号)过多少次,也没有地方记录唤醒线程(wait返回)过多少次。从实现上来说一个信号量可以是用mutex + counter + condition variable实现的。因为信号量有一个状态,如果想精准的同步,那么信号量可能会有特殊的地方。信号量可以解决条件变量中存在的唤醒丢失问题。

    (3)在Posix.1基本原理一文声称,有了互斥锁和条件变量还提供信号量的原因是:“本标准提供信号量的而主要目的是提供一种进程间同步的方式;这些进程可能共享也可能不共享内存区。互斥锁和条件变量是作为线程间的同步机制说明的;这些线程总是共享(某个)内存区。这两者都是已广泛使用了多年的同步方式。每组原语都特别适合于特定的问题”。尽管信号量的意图在于进程间同步,互斥锁和条件变量的意图在于线程间同步,但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间。应当根据实际的情况进行决定。信号量最有用的场景是用以指明可用资源的数量。

     

    参考:

    https://blog.csdn.net/puncha/article/details/8493862

    https://blog.csdn.net/hemmanhui/article/details/4417433

    https://blog.csdn.net/fullsail/article/details/8607566

    https://blog.csdn.net/nhn_devlab/article/details/6117239

    https://blog.csdn.net/ysu108/article/details/49508205

    展开全文
  • POSIX条件变量

    千次阅读 2015-06-08 11:40:58
    一.条件变量 ... 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,
  • QWaitCondition 条件变量

    千次阅读 2017-04-18 11:19:10
    在之前的文章中,我们已经讲过了很多种线程同步的方法,如互斥锁,信号量,读写锁等,今天我们再来学习一种线程同步的方法,条件变量
  • 条件变量解惑

    2014-02-16 17:05:00
    条件变量是用来等待而不是用来上锁的,通常条件变量和互斥锁同时使用。 条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。 条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放...
  • 条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和...
  • Linux下条件变量详解

    2019-08-17 20:39:06
    条件变量可以让线程在满足特定的条件下暂停(睡眠),需要与互斥量配合使用。 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 通过宏对条件变量初始化 int pthread_cond_init (pthread_cond_t cond,pthread_...
  • boost条件变量使用

    千次阅读 2017-03-16 13:54:37
    C++ BOOST库 条件变量[多线程通信]机制 笔记 2013年02月11日 ⁄ 综合 ⁄ 共 4566字 ⁄ 字号 小 中 大 ⁄ 评论关闭 1相关理念 (1)类名 条件变量和互斥变量都是boost库中被封装的类。   (2)条件变量...
  • Java 条件变量

    千次阅读 2013-02-19 17:14:47
    条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量。但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义。   这里的...
  • C++条件变量--std::condition_variable

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

    千次阅读 2017-03-13 20:23:43
    本文承接上文而来,主要是为了解决上一文中轮询所带来的 CPU 浪费问题。这里我们再把原问题复述一遍: 学生线程写作业,老师线程检查作业。...根据条件变量的这种特性,我们可以应用它来改写上一篇文章中...
  • linux C条件变量

    千次阅读 2019-05-30 14:07:06
    条件变量是一种线程间同步的机制,使用条件变量为了防止竞争,都会和一个互斥锁配合使用。 pthread_cond_wait()被调用阻塞住线程的时候,lock会被自动释放,当信号来的时候会自动上锁。 pthread_cond_signal()和.....
  • 有关条件变量

    2012-03-27 21:14:11
    条件变量:是用来通知共享数据状态信息的。可以使用条件变量来通知队列已空,或队列非空,或任何其他需要由线程处理的共享数据状态。 每个条件变量必须与一个特定的互斥量,一个谓词条件相关联。当线程等待条件...
  • 理解 Linux 条件变量

    千次阅读 2014-07-06 13:13:15
    理解 Linux 条件变量1 简介当多个线程之间因为存在某种依赖关系,导致只有当某个条件存在时,才可以执行某个线程,此时条件变量(pthread_cond_t)可以派上用场。比如:例1: 当系统不忙(这是一个条件)时,执行扫描...
  • 多线程 条件变量

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

    千次阅读 2018-06-24 21:21:45
    什么是条件变量 为何要用条件变量 条件变量的用法
  • 条件变量之虚假唤醒

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

    2014-04-16 23:01:24
    Cond.signal()向当个等待条件变量发通知 Broadcast();向所有的发消息
  • c++ 互斥量和条件变量

    万次阅读 2019-10-17 20:56:20
    线程同步时会遇到互斥量和条件变量配合使用的情况,下面看一下C++版的。 test.h #include <pthread.h> #include <iostream> class T_Mutex { public: T_Mutex() { pthread_mutex_init(&mutex...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 53,842
精华内容 21,536
关键字:

条件变量