zoukankan      html  css  js  c++  java
  • 生产者-消费者问题:介绍POSIX线程的互斥量和条件变量的使用

    全局初始化互斥量和条件变量(不全局也行,但至少要对线程启动函数可见,这样才能使用。)

    static pthread_cont_t cond = PTHREAD_COND_INITIALIZER;
    static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

    使用互斥量锁住一块代码方法如下(默认忽略pthread开头的函数的错误检查,即类似 int s = pthread_xxx(...); if (s != 0) { printErrorMsg(s, ...); }这种代码)

    pthread_mutex_lock(&mtx);
    // TODO: 在这里添加自定义代码(被锁住的代码)
    pthread_mutex_unlock(&mtx);

    被锁住的代码是同步执行的,这里定义代码的执行时间段为[start, end],即一段代码开始执行的时刻为start,结束执行的时刻为end。

    则任意两个线程执行lock和unlock之间的代码的时间段均不会重叠,也就是多线程执行被锁住的代码可以看做串行的,即总会有下述时间顺序:

    A线程lock成功 >>> A线程执行被锁住的代码 >>> A线程unlock >>> B线程lock成功 >>> B线程执行被锁住的代码 >>> B线程unlock

    条件变量的使用则是和互斥量挂钩的,若被锁住的代码存在等待操作,又不能确定等多久,此时就需要使用条件变量来等待。

    举个生产者-消费者的例子,线程A为狗的活动,线程B为主人的活动。狗进食和主人添加狗粮2个行为是互斥的,即不能同时发生,必须有先后顺序,代码类似下面

    void* consumer(void* arg)
    {
        while (1) {  // 狗持续进食
            pthread_mutex_lock(&mtx);
            // TODO: 狗进食的代码
            pthread_mutex_unlock(&mtx);
        }
    }
    
    void* producer(void* arg)  // 主人的一次喂食
    {
        pthread_mutex_lock(&mtx);
        // TODO: 主人喂食的代码
        pthread_mutex_unlock(&mtx);
    }

    而逻辑是,狗进食必须判断狗粮是否有剩余,也就是说狗进食的代码类似这样

    // 狗进食的代码
    if (hasFood())
        dog_eat();

    这段代码被线程启动函数consumer的lock和unlock锁住,于是每次都要上锁 >>> 检查是否有食物 >>> 解锁,若主人长时间不喂食,狗的线程将会浪费大量系统资源在这几个不必要的步骤上面,因此狗的正确做法是在没有食物时等待,然后主人添加食物时通知狗去吃。类似进程通信的signal()和kill()函数。

    等待过程在系统中相当于是阻塞,被阻塞的线程不会耗费系统资源。

    而条件变量则是如此使用的,这种思路下的代码如下

    void* consumer(void* arg)
    {
        while (1) {  // 狗持续进食
            pthread_mutex_lock(&mtx);
            if (!hasFood())
                pthread_cond_wait(&cond, &mtx); // 阻塞直到被唤醒
    
            dog_eat(); // 被唤醒则代表有食物了,可以吃了
            pthread_mutex_unlock(&mtx);
        }
    }
    
    void* producer(void* arg)  // 主人的一次喂食
    {
        pthread_mutex_lock(&mtx);
        addFood();
        pthread_mutex_unlock(&mtx);
        
        pthread_cond_signal(&cond); // 添加食物后发出信号,唤醒被阻塞的consumer函数
    }

    消费者线程执行pthread_cond_wait时,会暂时unlock(),这样其他线程(生产者)才能继续执行。

    生产者线程执行pthread_cond_signal时,会唤醒至少一条因为pthread_cond_wait而阻塞的线程,假如被唤醒的是执行consumer()函数的线程,pthread_cond_wait会返回,然后线程重新lock()。

    ——于是,其实这里的代码是有问题的,假如有一个线程先被唤醒,并吃空了狗粮,再之后,另一个线程才被唤醒,dog_eat()就会失败,因为狗粮被吃完了。

    因此被唤醒时需要再检查一次

            if (!hasFood())
                pthread_cond_wait(&cond, &mtx); // 阻塞直到被唤醒
    
            if (hasFood())
                dog_eat(); // 被唤醒则代表有食物了,可以吃了

    但是如果唤醒后hasFood()返回“假”时,我们没有处理而是继续执行,而正常逻辑是,没有食物的时候还是要等待,然后再被唤醒,如果被唤醒时还是没食物继续等待,所以正确的写法是用while循环

            while (!hasFood())
                pthread_cond_wait(&cond, &mtx); // 阻塞直到被唤醒
    
            dog_eat(); // 被唤醒则代表有食物了,可以吃了
  • 相关阅读:
    004-ant design -dispatch、request、fetch
    003-and design-dva.js 知识导图-02-Reducer,Effect,Subscription,Router,dva配置,工具
    002-and design-dva.js 知识导图-01JavaScript 语言,React Component
    003-and design-在create-react-app项目中使用antd
    002-and design-基于dva的基本项目搭建
    001-ant design安装及快速入门【基于纯antd的基本项目搭建】
    103-advanced-上下文
    102-advanced-代码分割
    101-advanced-React易用性,概述
    007-spring cache-缓存实现-02-springboot ehcahe2、ehcache3实现、springboot caffeine实现
  • 原文地址:https://www.cnblogs.com/Harley-Quinn/p/7307161.html
Copyright © 2011-2022 走看看