关于 门户网站 建设 请示,抓好网站建设工作,页面设计公司排名前十,4.1进行网站建设与推广目录
1.线程的同步
1.1.为什么需要线程的同步#xff1f;
2.2.条件变量的接口函数
2.生产消费模型
2.1 什么是生产消费模型
2.2.生产者消费者模型优点
2.3.为何要使用生产者消费者模型
3.基于BlockingQueue的生产者消费者模型
3.1为什么要将if判断变成while#xff…目录
1.线程的同步
1.1.为什么需要线程的同步
2.2.条件变量的接口函数
2.生产消费模型
2.1 什么是生产消费模型
2.2.生产者消费者模型优点
2.3.为何要使用生产者消费者模型
3.基于BlockingQueue的生产者消费者模型
3.1为什么要将if判断变成while
3.2.pthread_cond_wait函数调用的作用
代码
4.POSIX信号量
4.1.POISX信号量是什么
4.2.POISX信号量常见接口
4.3.POSIX信号量的核心PV操作
4.3.1、P操作等待信号量
4.3.2、V操作释放信号量
5.环形队列
5.1.生产消费模型搭建的原理
5.2.环形队列的具体实现
5.3.代码
5.4.多生产和多消费的并发性体现在 1.线程的同步
1.1.为什么需要线程的同步
上面我们讲解了线程的互斥问题但此时我们又发现了一个问题 如果某一个线程抢票能力过于强大把所有的票一个人都抢走了比如上面的线程4一个人就抢到了8088张票而线程2和线程3一张票都没有抢到这就造成了线程2和线程3的饥饿问题
在现实世界里这肯定是不行的秉持着公平公正的原则我们应该让这4个线程抢到的票都差不多才有实际意义。
所以互斥能解决抢票抢到负数的问题但是不能解决饥饿问题饥饿问题就需要线程同步去解决
通过条件变量我们可以实现线程的同步
2.2.条件变量的接口函数 int pthread_cond_init(pthread_cond_t *restrict cond , const pthread_condattr_t *restrictattr);:初始化接口 int pthread_cond_destroy(pthread_cond_t *cond):销毁接口 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);:在条件不满足时阻塞等待 int pthread_cond_broadcast(pthread_cond_t *cond);:条件满足唤醒所有线程开始竞争。 int pthread_cond_signal(pthread_cond_t *cond);条件满足唤醒一个线程。 条件变量需要一个线程队列和相应的通知机制才能保证线程同步
2.生产消费模型
2.1 什么是生产消费模型
总结一句话就是“321”原则
一个交易场所特定数据结构形式存在的一段内存空间两种角色生产角色消费角色生产线程消费线程三种关系生产与生产互斥关系 消费与消费互斥关系生产与消费。
1个交易场指的就是共享资源临界资源有多个厂商生产者和多个用户消费者所以这就是我们常说的多线程的同步和互斥问题。
超市是什么临时保存数据的“内存空间”——某种数据结构对象。
商品是什么就是数据 2.2.生产者消费者模型优点
解耦支持并发支持忙闲不均
2.3.为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯而通过阻塞队列来进行通讯所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
3.基于BlockingQueue的生产者消费者模型 3.1为什么要将if判断变成while
如果生产者只生产了一份但是叫醒了5个消费者当一个消费者竞争锁结束取走仅有的一份商品那接下来的4个消费者就会看到空的队列如果是if因为之前已经判断过所以会直接执行下面取空的队列因此会直接报错但是如果是while的话仍需要判断队列是否已经满了因为当等待的线程被唤醒的时候继续从当前的位置进行执行代码
3.2.pthread_cond_wait函数调用的作用
a. 让调用线程等待
b. 自动释放曾经持有的_mutex锁
c. 当条件满足线程唤醒pthread_cond_wait要求线程必须重新竞争_mutex锁竞争成功方可返回
代码
#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__#include iostream
#include string
#include queue
#include pthread.htemplate typename T
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() _cap;}bool IsEmpty(){return _block_queue.empty();}
public:BlockQueue(int cap) : _cap(cap){_productor_wait_num 0;_consumer_wait_num 0;pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_product_cond, nullptr);pthread_cond_init(_consum_cond, nullptr);}void Enqueue(T in) // 生产者用的接口{pthread_mutex_lock(_mutex);while(IsFull()) // 保证代码的健壮性{// 生产线程去等待,是在临界区中休眠的你现在还持有锁呢// 1. pthread_cond_wait调用是: a. 让调用线程等待 b. 自动释放曾经持有的_mutex锁 c. 当条件满足线程唤醒pthread_cond_wait要求线性// 必须重新竞争_mutex锁竞争成功方可返回// 之前安全_productor_wait_num;pthread_cond_wait(_product_cond, _mutex); // 只要等待必定会有唤醒唤醒的时候就要继续从这个位置向下运行_productor_wait_num--;// 之后安全}// 进行生产// _block_queue.push(std::move(in));// std::cout in std::endl;_block_queue.push(in);// 通知消费者来消费if(_consumer_wait_num 0)pthread_cond_signal(_consum_cond); // pthread_cond_broadcastpthread_mutex_unlock(_mutex);}void Pop(T *out) // 消费者用的接口 --- 5个消费者{pthread_mutex_lock(_mutex);while(IsEmpty()) // 保证代码的健壮性{// 消费线程去等待,是在临界区中休眠的你现在还持有锁呢// 1. pthread_cond_wait调用是: a. 让调用进程等待 b. 自动释放曾经持有的_mutex锁_consumer_wait_num;pthread_cond_wait(_consum_cond, _mutex); // 伪唤醒_consumer_wait_num--;}// 进行消费*out _block_queue.front();_block_queue.pop();// 通知生产者来生产if(_productor_wait_num 0)pthread_cond_signal(_product_cond);pthread_mutex_unlock(_mutex);// pthread_cond_signal(_product_cond);}~BlockQueue(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_product_cond);pthread_cond_destroy(_consum_cond);}private:std::queueT _block_queue; // 阻塞队列是被整体使用的int _cap; // 总上限pthread_mutex_t _mutex; // 保护_block_queue的锁pthread_cond_t _product_cond; // 专门给生产者提供的条件变量pthread_cond_t _consum_cond; // 专门给消费者提供的条件变量int _productor_wait_num;int _consumer_wait_num;
};#endif 我们之前学习了基于条件变量和阻塞队列实现空间可以动态分配的生产消费者模型今天我们来用POSIX信号量基于固定大小的环形队列重写这个程序。
4.POSIX信号量
4.1.POISX信号量是什么
信号量本质是一个计数器可以在初始化时对设置资源数量进程 / 线程 可以获取信号量来对资源进行操作和结束操作可以释放信号量
POSIX信号量和SystemV信号量作用相同都是用于同步操作达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
4.2.POISX信号量常见接口
信号量初始化 #include semaphore.h int sem_init(sem_t *sem, int pshared, unsigned int value); 参数分别为 sem_t *sem传入信号量的地址 pshared传入0值表示线程间共享传入非零值表示进程间共享。 value信号量的初始值计数器的初始值 信号量销毁 #include semaphore.h int sem_destroy(sem_t *sem) 4.3.POSIX信号量的核心PV操作
POSIX信号量的PV操作是信号量机制中的核心它们分别代表了对信号量的等待P操作和释放V操作。
4.3.1、P操作等待信号量
P操作也称为“申请资源”或“等待信号量”操作用于尝试减少信号量的值。当线程或进程需要访问某个临界资源时它会执行P操作来申请信号量。 函数原型 int sem_wait(sem_t *sem); 其中sem是指向要等待的信号量的指针。 操作过程 如果信号量的当前值大于0那么P操作会将信号量的值减1并立即返回表示申请资源成功。如果信号量的当前值为0那么执行P操作的线程或进程将被阻塞直到信号量的值变为大于0即有其他线程或进程释放了信号量。此时被阻塞的线程或进程会重新尝试P操作如果成功则信号量的值再次减1。 返回值 成功时返回0。失败时返回-1并设置errno来指示错误类型。
4.3.2、V操作释放信号量
V操作也称为“释放资源”或“发布信号量”操作用于增加信号量的值。当线程或进程完成对临界资源的访问后它会执行V操作来释放信号量。 函数原型 int sem_post(sem_t *sem); 其中sem是指向要释放的信号量的指针。 操作过程 V操作会将信号量的值加1。如果有线程或进程因为信号量的值为0而被阻塞在P操作上那么V操作会唤醒其中一个被阻塞的线程或进程使其能够继续执行P操作并访问临界资源。 返回值 总是返回0表示成功。V操作永远不会阻塞。
注意PV操作是原子的这意味着它们在执行过程中不会被其他线程或进程的打断。这保证了信号量机制的正确性和可靠性。
5.环形队列
5.1.生产消费模型搭建的原理
环形队列底层也是普通数组
生产者和消费者指向同一位置有两种情况
队列为空让生产者先跑队列为满让消费者先跑
环形队列当队列不为空或者满的时候真正实现了多线程同步。当然生产者不能把消费者套一个圈消费者不能超过生产者。这些都可以通过POSIX信号量的特性实现~
5.2.环形队列的具体实现
首先需要区分生产者和消费者生产者只关注空间消费者只关注资源。生产者和消费者都需要进行PV操作生产者对应的将任务加入队列消费者对应的取出队列里的任务。
Consumer线程不断从环形队列中取出Task对象执行其操作并打印消费结果。Productor线程则持续生成新的Task对象并将其放入队列中同时打印出生产信息。
并且还需要两把锁分别给生产者和消费者保证多线程并发的线程安全。
5.3.代码
#pragma once#include iostream
#include string
#include vector
#include semaphore.h
#include pthread.htemplatetypename T
class RingQueue
{
private:void P(sem_t sem){sem_wait(sem);}void V(sem_t sem){sem_post(sem);}void Lock(pthread_mutex_t mutex){pthread_mutex_lock(mutex);}void Unlock(pthread_mutex_t mutex){pthread_mutex_unlock(mutex);}
public:RingQueue(int cap): _ring_queue(cap), _cap(cap), _productor_step(0), _consumer_step(0){sem_init(_room_sem, 0, _cap);sem_init(_data_sem, 0, 0);pthread_mutex_init(_productor_mutex, nullptr);pthread_mutex_init(_consumer_mutex, nullptr);}void Enqueue(const T in){// 生产行为P(_room_sem);Lock(_productor_mutex);// 一定有空间_ring_queue[_productor_step] in; // 生产_productor_step % _cap;Unlock(_productor_mutex);V(_data_sem);}void Pop(T *out){// 消费行为P(_data_sem);Lock(_consumer_mutex);*out _ring_queue[_consumer_step];_consumer_step % _cap;Unlock(_consumer_mutex);V(_room_sem);}~RingQueue(){sem_destroy(_room_sem);sem_destroy(_data_sem);pthread_mutex_destroy(_productor_mutex);pthread_mutex_destroy(_consumer_mutex);}
private:// 1. 环形队列std::vectorT _ring_queue;int _cap; // 环形队列的容量上限// 2. 生产和消费的下标int _productor_step;int _consumer_step;// 3. 定义信号量sem_t _room_sem; // 生产者关心sem_t _data_sem; // 消费者关心// 4. 定义锁维护多生产多消费之间的互斥关系pthread_mutex_t _productor_mutex;pthread_mutex_t _consumer_mutex;
};
5.4.多生产和多消费的并发性体现在
消费者在处理任务的时候可以并发
所以多生产和多消费的意义不在于向队列中生产再从队列中拿走。而在于生产前我们可以多线程并发获取原始任务生产后被我们的消费者拿走任务后可以多线程并发式的去执行各自的任务。这才是多生产多消费的意义
多生产多消费的模型主要在于多个生产者去竞争一个名额然后进行加锁多个消费者竞争一个名额然后进行加锁所以最终还是会变成单生产单消费