线程同步之 生产者消费者模型详解

简介: 前言 博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产者消费者模型讲一讲,希望大家能看懂(没有现成和锁知识的朋友不要急,这部分是写给有基础的朋友看的,这些知识博主都会慢慢的讲到)。前言 博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产

前言

      博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产者消费者模型讲一讲,希望大家能看懂(没有现成和锁知识的朋友不要急,这部分是写给有基础的朋友看的,这些知识博主都会慢慢的讲到)。


什么是模型?

模型就是要解决某个问题的固定方法或套路,所以有时候我们学会一点模型是很有必要的。

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

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

      换一句话来说就是,比如说你在学校上课,好不容易熬到了放学,此时你肯定会和你的小伙伴一起去食堂吃饭(土豪除外,土豪不去食堂),但是因为之前食堂停水停电,导致,你们放学了才开始做饭,所以现在就会面临一个问题。

      当食堂大叔做好饭之后会把饭放在指定的饭盒里面,但是食堂大叔做饭的速度可能比不上你和你小伙伴吃饭的速度(或者大叔做饭的速度太快导致们根本吃不过大叔的做饭速度)。这也就是我们的生产者消费者模型了。

所以博主先列出一些概念,更容易使大家理解:


生产者:产生数据的一方(食堂大叔做饭)

消费者:处理数据的一方(你和你的小伙伴吃饭)


我们会遇到的问题

双方处理数据的速度可能不同,这样就会导致总由一方吃处于阻塞状态。(也就是博主上面所说的吃饭的问题)可能博主的逻辑不是很严谨,但是将就着看吧。


要用到的函数解析:

int pthread_cond_wait


 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);
我们要知道的是在调用这个函数之前我们要做前准备好的事情:
1.mutex_t mutex 定义互斥量
2.init 初始化
3.lock 加锁
4.cond_t has_product 定义条件变量
5.inti  初始化


pthread_cond_signal


int pthread_cond_signal(pthread_cond_t *cond);
唤醒至少一个阻塞在条件变量上的线程


提到了这么多的东西了,接下来博主就给出生产者消费者模型的代码(里面有很详细的解释了,大家应该都能看懂,博主就不在做GIF动画了)


/*借助条件变量模拟 生产者-消费者 问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
/*链表作为公享数据,需被互斥量保护*/
struct msg {
    struct msg *next;
    int num;
};
struct msg *head;
struct msg *mp;
/* 静态初始化 一个条件变量 和 一个互斥量  完成 1 2   4 5 */
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer(void *p)
{
    while(true){
        pthread_mutex_lock(&lock);  //加锁  完成 3 
        while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗
            pthread_cond_wait(&has_product, &lock);   //此时会阻塞等待 等生产者传入之后才会继续工作
        /*  1.阻塞等待条件变量cond(参1)满足  
          2.释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
            注意:   1和2这两步为一个原子操作。
          3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);*/
        }
    mp = head;      
        head = mp->next;    //模拟消费掉一个产品
        pthread_mutex_unlock(&lock);  //解锁
        printf("-Consume ---%d\n", mp->num);
        free(mp); //create 和 free 要成双成对出现
        mp = NULL;  
        sleep(rand() % 5);  //随机随眠让 CUP 转移
    }
}
void *producer(void *p)
{
   while(true) {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;        //模拟生产一个产品
        printf("-Produce ---%d\n", mp->num);
        pthread_mutex_lock(&lock);  //加锁(不用担心死锁,即使是消费者先调用,在pthread_cond_wait()之后也会将之前获得的锁给释放!
      //头插法
    mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);  //解锁
        pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程(生产者线程)唤醒
        sleep(rand() % 5);  //随机随眠让 CUP 转移
    }
}
int main(int argc, char *argv[])
{
    pthread_t pid, cid; //pid-->生产者    cid -->消费者
    srand(time(NULL));
  /* 调用各自的主控函数 */
    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);
  //回收各自的线程!
    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}

有朋友会遇到这种问题:

我为什么写的不能实现同步呢?就只是一个生产者在那干活

博主认为最有可能的原因是因为时序竞态,这种解决办法很简单加sleep就行了。

或者是 博主上面的两个函数没使用好导致这种情况,要记住 wait等的是生产的信号,signal发的是生产者的信号去唤醒阻塞的消费者。


目录
相关文章
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
2月前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
25 1
|
2月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
2月前
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
33 0
|
3天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
12 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
32 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
25 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
41 2