工商注册官方网站,潜江资讯网二手车,受欢迎的惠州网站建设,安卓应用目录
一、什么是生产者消费者模型
二、为什么要引入生产者消费者模型#xff1f;
三、详解生产者消费者模型 编辑
生产者和生产者、消费者和消费者、生产者和消费者#xff0c;它们之间为什么会存在互斥关系#xff1f;
生产者和消费者之间为什么会存在同步关系…目录
一、什么是生产者消费者模型
二、为什么要引入生产者消费者模型
三、详解生产者消费者模型 编辑
生产者和生产者、消费者和消费者、生产者和消费者它们之间为什么会存在互斥关系
生产者和消费者之间为什么会存在同步关系
为什么生产者与消费者之间需要一个交易场所
四、基于阻塞队列的生产者消费者模型
生产者消费者模型的使用场景示例
五、理解生产者消费者模型代码 一、什么是生产者消费者模型
生产者-消费者模型是一种常见的并发设计模式用于处理在生产和消费任务之间的协调。这个模型主要用来解决在多线程或多进程环境中生产者和消费者之间的同步和数据共享问题。
在这个模型中
生产者负责生成数据或任务并将其放入一个共享的缓冲区也称为队列中。消费者从共享缓冲区中取出数据或任务并进行处理。
缓冲区的作用是实现生产者和消费者之间的解耦使得生产者和消费者可以在不同的速度下独立工作。
二、为什么要引入生产者消费者模型
在没有这种模型的情况下生产者和消费者可能会遇到以下问题 资源浪费如果生产者生产速度过快而消费者消费速度跟不上可能会导致生产出的产品通常是数据或任务被丢弃造成资源浪费。资源不足如果消费者消费速度过快而生产者生产速度跟不上消费者可能会因为等待新资源而处于空闲状态造成资源不足。竞态条件在没有适当的同步机制的情况下多个生产者或消费者同时访问共享资源如队列可能会导致数据不一致或状态错误。 死锁如果生产者和消费者在等待对方释放资源时都阻塞了可能会导致死锁即系统无法继续前进。饥饿某些消费者可能因为其他消费者不断地获取资源而长时间得不到服务这种现象称为饥饿。 生产者-消费者模型通过引入缓冲区如队列和同步机制如信号量、互斥锁来解决这些问题。模型通常包含以下几个关键组件 生产者负责生成数据或任务。消费者负责处理数据或任务。缓冲区存储生产者生产的数据供消费者使用。同步机制确保生产者和消费者之间的协调避免竞态条件和死锁。 通过这些组件生产者-消费者模型可以有效地管理资源确保生产者不会在缓冲区满时生产消费者不会在缓冲区空时消费从而实现生产者和消费者之间的平衡和同步。
三、详解生产者消费者模型
生产者消费者模型是多线程同步与互斥的一个经典场景其特点如下 三种关系 生产者和生产者互斥关系、消费者和消费者互斥关系、生产者和消费者互斥关系、同步关系。两种角色 生产者和消费者。通常由进程或线程承担一个交易场所 通常指的是内存中的一段缓冲区。如阻塞队列、环形队列等 我们在用代码编写生产者消费者模型的时本质就是对这三个特点进行维护。 生产者和生产者、消费者和消费者、生产者和消费者它们之间为什么会存在互斥关系 生产者将生产的数据放入容器中而消费者又会从容器中取出数据这就造成了在同一时刻中容器中的数据可能被多个执行流访问。而该容器其实就是临界资源对于临界资源我们必须保护对临界资源操作的原子性否则容易造成数据错乱。
因此我们必须保证生产者和消费者访问临界资源的操作是串行的。每次访问我们只允许一个执行流进入临界区。如此我们就保证了临界资源的安全。
所以在访问临界区资源之前无论是生产者还是消费者必须先去竞争保护临界资源的那把互斥锁。即生产者和消费者会进行同一把锁的竞争 生产者和消费者之间为什么会存在同步关系 如果让生产者一直生产那么当生产者生产的数据将容器塞满后生产者再生产数据就会生产失败。反之让消费者一直消费那么当容器当中的数据被消费完后消费者再进行消费就会消费失败。
虽然这样不会造成任何数据不一致的问题但是这样会引起另一方的饥饿问题是非常低效的。我们应该让生产者和消费者访问该容器时具有一定的顺序性比如让生产者先生产然后再让消费者进行消费。
当缓冲区为满时生产者需要在自己的条件变量下阻塞等待直到有消费者进行消费缓冲区有空间剩余时才会继续与消费者竞争临界资源的管理权。
当缓冲区为空时消费者需要在自己的条件变量下阻塞等待直到有生产者进行生产缓冲区有数据时才会继续与生产者竞争临界资源的管理权。
【注意 互斥关系保证的是数据的正确性而同步关系是为了让多线程之间协同起来。】 为什么生产者与消费者之间需要一个交易场所 生产者与消费者之间需要一个交易场所通常称为缓冲区Buffer是因为这个缓冲区在多线程环境中起到了至关重要的作用。以下是缓冲区的几个关键作用 协调生产和消费缓冲区作为生产者和消费者之间的中介允许生产者在缓冲区有空间时生产数据消费者在缓冲区有数据时消费数据。这种协调机制避免了生产者和消费者之间的直接竞争确保了生产和消费的有序进行。 解耦生产者和消费者缓冲区使得生产者和消费者不必同时运行。生产者可以在没有消费者等待的情况下生产数据消费者也可以在没有生产者生产的情况下消费数据。这种解耦提高了系统的灵活性和效率。 防止数据丢失在没有缓冲区的情况下如果生产者生产速度过快消费者可能无法及时消费导致数据丢失。缓冲区提供了一个存储空间可以暂时保存生产者生产的数据直到消费者准备好消费。 提高并行性缓冲区允许生产者和消费者以不同的速度独立工作提高了系统的并行性和吞吐量。生产者可以快速生产数据而消费者可以根据自己的速度消费数据两者互不干扰。 避免死锁和饥饿通过适当的同步机制如信号量和条件变量缓冲区可以避免死锁和饥饿问题。生产者在缓冲区满时等待消费者在缓冲区空时等待这些等待条件可以通过同步机制得到管理确保所有线程都能在适当的时候获得资源。 实现负载均衡缓冲区可以平滑生产者和消费者之间的负载波动。在生产者负载高时缓冲区可以存储额外的数据而在消费者负载高时缓冲区可以提供足够的数据供消费。 简化线程管理缓冲区提供了一个明确的接口使得线程管理更加简单。生产者和消费者只需要关注缓冲区的状态而不需要直接管理其他线程。
总之缓冲区作为生产者与消费者之间的交易场所是实现高效、稳定和可扩展的多线程系统的关键组件。它通过协调生产和消费、解耦生产者和消费者、防止数据丢失、提高并行性、避免死锁和饥饿以及实现负载均衡等方式提高了系统的整体性能和可靠性。
四、基于阻塞队列的生产者消费者模型 什么是阻塞队列它的作用是什么 在多线程编程中阻塞队列Blocking Queue是一种常用于实现生产者和消费者模型的数据结构。
生产者将数据放入队列消费者从队列中取出数据。
当队列为空时消费者会阻塞等待直到有数据可用当队列满时生产者会阻塞等待直到有空间可用。
通过阻塞操作阻塞队列可以有效地管理资源避免过度生产或消费从而提高并发性能。 #pragma once
#include pthread.h
#include iostream
#include queue
#include Thread.hppstatic const int MAX_CAPACITY 6;template typename T
class BlockQueue
{
private:std::queueT _block_queue;//阻塞队列临界资源int _max_capacity;//队列最大容量pthread_mutex_t _mutex;//生产者和消费者之间需要满足互斥关系。因为当生产者在生产时消费者不能去消费否则容易造成临界资源错乱。反之亦然pthread_cond_t _producer_cond;//当生产资源达到最大容量的时候生产者需要在生产者的条件变量下等待当有空余空间时在进行生产由消费者告知生产者来生产pthread_cond_t _consumer_cond;//当生产资源被消费完之后消费者需要在消费者条件变量下等待直到新的资源被生产由生产者唤醒消费者来进行消费//在生产资源未到最大容量和生产资源未空时消费者和生产者形成互斥关系竞争临界资源的管理权
public://构造BlockQueue(int capacity MAX_CAPACITY):_max_capacity(capacity){pthread_mutex_init(_mutex, nullptr);//初始化锁pthread_cond_init(_producer_cond, nullptr);//初始化条件变量pthread_cond_init(_consumer_cond, nullptr);}void Push(const T in){//生产者和消费者竞争同一把锁pthread_mutex_lock(_mutex);while(IsFull())//为什么用while不用if 防止当有多个生产者时使用broadcast会造成多个线程被唤醒而此时多个线程已经经过了if判断。//使用if只能判断一次无法避免时间差所带来的条件改变因此需要循环检查。{//生产者在生产者的条件变量处等待等待时释放锁。被唤醒时重新参与锁的竞争pthread_cond_wait(_producer_cond, _mutex);}//走到这里一定不为满_block_queue.push(in);pthread_mutex_unlock(_mutex);//走到这个地方说明此时阻塞队列中一定有数据可以唤醒消费者线程pthread_cond_signal(_consumer_cond);}void Pop(T* out) //输出型参数将数据带出来{//生产者和消费者竞争同一把锁pthread_mutex_lock(_mutex);while(IsEmpty()){//消费者在消费者的条件变量处等待等待时释放锁被唤醒时重新参与锁的竞争。竞争到锁之后才能返回pthread_cond_wait(_consumer_cond, _mutex);}//走到这里一定不为空*out _block_queue.front();_block_queue.pop();pthread_mutex_unlock(_mutex);//走到这个地方说明阻塞队列中一定有剩余空间可以唤醒生产者pthread_cond_signal(_producer_cond);}bool IsFull(){return _block_queue.size() _max_capacity;}bool IsEmpty(){return _block_queue.empty();}//析构~BlockQueue(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_producer_cond);pthread_cond_destroy(_consumer_cond);}
}; 需要注意的是条件变量的等待必须要放在while循环中进行条件判断。 在多线程环境中多个线程可能同时操作共享资源。即使一个线程被唤醒也可能因为其他线程的操作使得条件不再满足。因此在条件变量的等待过程中必须持续检查条件是否真的符合期望确保线程在安全的条件下继续执行。在pthread_cond_wait函数的等待队列中可能存在多个等待线程。如果使用 if 进行条件判断仅能在该执行流执行管理资源的代码前进行判断。假如此时阻塞队列中恰好生产了一个数据而条件变量的等待队列中恰好有多个线程被其他线程使用pthread_cond_broadcast函数同时唤醒无论 为什么不使用 if 判断呢 pthread_cond_wait函数是让当前执行流进行等待的函数是函数就意味着有可能调用失败调用失败后该执行流就会继续往后执行。其次在多消费者的情况下当生产者生产了一个数据后如果使用pthread_cond_broadcast函数唤醒消费者此时在pthread_cond_wait函数的等待队列中可能存在多个等待线程因此就会一次性唤醒多个消费者但待消费的数据只有一个此时其他消费者就被伪唤醒了。为了避免出现上述情况我们就要让线程被唤醒后再次进行判断确认是否真的满足生产消费条件因此这里必须要用while进行判断。 生产者消费者模型的使用场景示例
生产者承担的工作不断地将创建的任务放入阻塞队列中。
消费者承担的工作不断地从阻塞队列中提取任务并进行执行。 阻塞队列要让生产者线程向队列中Push数据让消费者线程从队列中Pop数据因此这个阻塞队列必须要让这两个线程同时看到所以我们在创建生产者线程和消费者线程时需要将该阻塞队列作为线程执行例程的参数进行传入。
#include Blocking_Queue.hpp
#include functional
#include unistd.h
#include ctime
using task_t std::functionvoid();void download()
{std::cout DownLooad ...... std::endl;
}void *Productor(void *block_queue)
{BlockQueuetask_t *bq static_castBlockQueuetask_t *(block_queue);while (true){std::cout Producing ...... std::endl;bq-Push(download);sleep(1);}return (void*)0;
}void *Consumer(void *block_queue)
{BlockQueuetask_t *bq static_castBlockQueuetask_t *(block_queue);while (true){sleep(1);std::cout Consuming ...... std::endl;task_t out;bq-Pop(out);out();}return (void*)0;
}int main()
{BlockQueuetask_t block_queue;pthread_t productor_tid, consumer_tid;pthread_create(productor_tid, nullptr, Productor, static_castvoid *(block_queue));pthread_create(consumer_tid, nullptr, Consumer, static_castvoid *(block_queue));pthread_join(productor_tid, nullptr);pthread_join(consumer_tid, nullptr);return 0;
}
由于代码中生产者是每隔一秒生产一个数据而消费者是每隔一秒消费一个数据因此运行代码后我们可以看到生产者和消费者的执行步调是一致的。
需要注意的是由于我们将BlockingQueue当中存储的数据进行了模板化此时就可以让BlockingQueue当中存储其他类型的数据。这点可根据需求自行修改。
五、理解生产者消费者模型代码
看完上述内容后同学们可能会有这种疑惑生产者消费者模型不是为了实现高并发而设计的吗为什么在生产者创建任务时和消费者提取任务时是串行的而不是并行的
在回答这个问题之前我们需要了解一下什么是并发什么又是并行。 并发Concurrency 定义并发指的是系统能够处理多个任务的能力这些任务可能是同时进行的也可能是交替进行的。在并发的场景下任务可以在同一时间段内交替执行但不一定是同时的。并发更关注的是任务的切换和管理。 并行Parallelism 定义并行指的是系统能够真正地同时执行多个任务。在并行的场景下多个任务在同一时间段内在不同的处理器或计算核心上同时执行。并行更多地关注的是计算任务的真正同时处理。 在生产者消费者模型中生产者和消费者对阻塞队列的操作仅仅是向阻塞队列中添加和提取数据而阻塞队列是共享资源我们必须对其进行线程安全的保护让各个执行流串行地去执行临界区代码保证对临界资源操作的原子性。
而添加与提取任务恰恰是生产者消费者模型中执行比较快速的操作。我们可以假设此时有多个生产者线程此时只能有一个生产者线程能够进入临界区中。那么在同一时刻其他的生产者线程一定都在等待进入临界区中呢答案是否定的
我们需要知道今天我们只是使用该模型执行了简单的任务。当遇见复杂任务时生产者需要时间来构建任务反之消费者也需要时间来执行任务 在某个生产者线程向阻塞队列中添加任务时其他生产者线程可能在等待也可能在进行任务的生产。消费者线程亦然
由此我们可以看出上述代码实现的生产者消费者模型并不是低效的。相反它有效地管理共享资源的访问、合理安排线程的任务并确保系统在处理复杂任务时仍能保持高效和稳定。同时解决了生产者与消费者可能出现的忙闲不均的问题实现了负载均衡。