zoukankan      html  css  js  c++  java
  • Linux多线程实践(六)使用Posix条件变量解决生产者消费者问题

    前面的一片文章我们已经讲过使用信号量解决生产者消费者问题。那么什么情况下我们须要引入条件变量呢?

    这里借用  http://www.cnblogs.com/ngnetboy/p/3521547.html 的解释:

    如果有共享的资源sum,与之相关联的mutex lock_s.如果每一个线程对sum的操作非常easy的,sum的状态无关,比方仅仅是sum++.那么仅仅用mutex足够了.程序猿仅仅要确保每一个线程操作前,取得lock,然后sum++,unlock就可以.每一个线程的代码将像这样:

    add()
    {
       pthread_mutex_lock(lock_s);
       sum++;
       pthread_mutex_unlock(lock_s);
    }

    假设操作比較复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零这样的情况下,假设仅仅用mutex, t3须要一个循环,每一个循环里先取得lock_s,然后检查sum的状态,假设sum>=100,则打印并清零,然后unlock.假设sum<100,unlock,sleep()本线程合适的一段时间

    这个时候,t0,t1,t2的代码不变,t3的代码例如以下:

    print()
    {
       while (1)
      {
          pthread_mutex_lock(lock_s);
          if(sum<100)
         {
             printf(“sum reach 100!”);
             pthread_mutex_unlock(lock_s);
         }
      else
      {
           pthread_mutex_unlock(lock_s);
           my_thread_sleep(100);
           return OK;
       }
     }
    }

    这样的办法有两个问题

    1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,仅仅是lockunlock,然后sleep().这浪费了CPU处理时间.

    2) 为了节省CPU处理时间,t3会在探測到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t4才会醒过来.

    3) 这样,程序猿在设置sleep()时间的时候陷入两难境界,设置得太短了节省不了资源,太长了又减少响应速度.真是难办啊!

    这个时候,condition variable,从天而降,解救了焦头烂额的你.

    你首先定义一个condition variable.

    pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER;

    t0,t1,t2的代码仅仅要后面加两行,像这样:

    add()
    {
       pthread_mutex_lock(lock_s);
       sum++;
       pthread_mutex_unlock(lock_s);
       if(sum>=100)
       pthread_cond_signal(&cond_sum_ready);
    }
    而t3的代码则是
    print
    {
       pthread_mutex_lock(lock_s);
       while(sum<100)
       pthread_cond_wait(&cond_sum_ready, &lock_s);
       printf(“sum is over 100!”);
       sum=0;
       pthread_mutex_unlock(lock_s);
       return OK;
    }

    注意两点:

    1) thread_cond_wait()之前,必须先lock相关联的mutex, 由于假如目标条件未满足,pthread_cond_wait()实际上会unlockmutex, 然后block,在目标条件满足后再又一次lockmutex, 然后返回.

    2) 为什么是while(sum<100),而不是if(sum<100) ?这是由于在pthread_cond_signal()pthread_cond_wait()返回之间,有时间差,如果在这个时间差内,还有另外一个线程t4又把sum降低到100下面了,那么t3pthread_cond_wait()返回之后,显然应该再检查一遍sum的大小.这就是用 while的用意


    线程间的同步技术。主要以相互排斥锁和条件变量为主,条件变量和相互排斥所的配合使用能够非常好的处理对于条件等待的线程间的同步问题


    Posix条件变量经常使用API:

    int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);  
    int pthread_cond_destroy(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_signal(pthread_cond_t *cond);  
    int pthread_cond_broadcast(pthread_cond_t *cond); 

    通常条件变量须要和相互排斥锁同一时候使用, 利用相互排斥量保护条件变量;条件的检測是在相互排斥锁的保护下进行的。

    假设一个条件为假,一个线程自己主动堵塞,并释放等待状态改变的相互排斥锁。假设还有一个线程改变了条件,它就发送信号给关联的条件变量, 并唤醒一个或多个等待在该条件变量上的线程,这些线程将又一次获得相互排斥锁,又一次评价条件。假设将条件变量放到共享内存中, 而两进程可共享读写这段内存,则条件变量能够被用来实现两进程间的线程同步。

    条件变量的使用规范:

    (一)、等待条件代码
    pthread_mutex_lock(&mutex);
    while (条件为假)
    pthread_cond_wait(cond, mutex);
    改动条件
    pthread_mutex_unlock(&mutex);
    
    
    (二)、给条件发送通知代码
    pthread_mutex_lock(&mutex);
    设置条件为真
    pthread_cond_signal(cond);
    pthread_mutex_unlock(&mutex);
    注意是while而不是if,原因是在信号的中断后还能正常执行。

    解决生产者消费者问题(无界缓冲区):

    #include <unistd.h>
    #include <sys/types.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    #define CONSUMERS_COUNT 2
    #define PRODUCERS_COUNT 1
    
    pthread_mutex_t g_mutex;
    pthread_cond_t g_cond;
    
    pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT];
    
    int nready = 0;
    
    void *consume(void *arg)
    {
        int num = (int)arg;
        while (1)
        {
            pthread_mutex_lock(&g_mutex);
            while (nready == 0)
            {
                printf("%d begin wait a condtion ...
    ", num);
                pthread_cond_wait(&g_cond, &g_mutex);
            }
    
            printf("%d end wait a condtion ...
    ", num);
            printf("%d begin consume product ...
    ", num);
            --nready;
            printf("%d end consume product ...
    ", num);
            pthread_mutex_unlock(&g_mutex);
            sleep(1);
        }
        return NULL;
    }
    
    void *produce(void *arg)
    {
        int num = (int)arg;
        while (1)
        {
            pthread_mutex_lock(&g_mutex);
            printf("%d begin produce product ...
    ", num);
            ++nready;
            printf("%d end produce product ...
    ", num);
            pthread_cond_signal(&g_cond);
            printf("%d signal ...
    ", num);
            pthread_mutex_unlock(&g_mutex);
            sleep(1);
        }
        return NULL;
    }
    
    int main(void)
    {
        int i;
    
        pthread_mutex_init(&g_mutex, NULL);
        pthread_cond_init(&g_cond, NULL);
    
    
        for (i = 0; i < CONSUMERS_COUNT; i++)
            pthread_create(&g_thread[i], NULL, consume, (void *)i);
    
        sleep(1);
    
        for (i = 0; i < PRODUCERS_COUNT; i++)
            pthread_create(&g_thread[CONSUMERS_COUNT + i], NULL, produce, (void *)i);
    
        for (i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
            pthread_join(g_thread[i], NULL);
    
        pthread_mutex_destroy(&g_mutex);
        pthread_cond_destroy(&g_cond);
    
        return 0;
    }







  • 相关阅读:
    简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)
    使用postgre数据库实现树形结构表的子-父级迭代查询,通过级联菜单简单举例
    关于cas-client单点登录客户端拦截请求和忽略/排除不需要拦截的请求URL的问题(不需要修改任何代码,只需要一个配置)
    javacpp-opencv图像处理系列:国内车辆牌照检测识别系统(万份测试车牌识别准确率99.7%以上,单次平均耗时39ms)
    最全面的Java字节byte操作,处理Java基本数据的转换及进制转换操作工具,流媒体及java底层开发项目常用工具类
    基于java.util.logging实现轻量级日志记录库(增加根据当前类class初始化,修复线程池模型(javaEE)下的堆栈轨迹顺序与当前调用方法不一致问题)
    java原生实现屏幕设备遍历和屏幕采集(捕获)等功能
    用java实现简单快速的webservice客户端/数据采集器(支持soap1.1和soap1.2标准,支持utf-8编码)
    javacpp-opencv图像处理3:使用opencv原生方法遍历摄像头设备及调用(增加实时帧率计算方法)
    mybatis批量增、删、改(更新)操作oracle和mysql批量写法小记
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7081259.html
Copyright © 2011-2022 走看看