本文重点:
生产者和消费者模型:
信号量的原理及实现流程:
一.生产者和消费者模型及实现原理:
1.锁等待:
我们知道在实现线程间同步的过程中条件变量通过提供线程等待与唤醒线程的实现线程同步,用户在判断条件不满足的情况下提供等待功能,线程什么时候等待,需要一个判断条件;而这个判断条件也是一个临界资源,条件变量的条件判断应该是一个循环判断:
多个顾客线程若被唤醒,只有一个顾客可以加锁,其他的顾客线程将阻塞在加锁上(而不是条件变量的等待);
第一个加锁的顾客开始吃面,吃完面后进行解锁,这时候获取到锁的线程有可能是一个顾客线程,因为没有再次判断有没有面,而是直接吃面;但是面已经被第一个顾客吃掉了,因此逻辑出现错误;应该加锁之后重新再次判断是否有面;
不同的角色应该等待在不同的条件变量中:
在有多个厨师线程和顾客线程的时候,若是顾客和厨师线程都等待在同一条件变量的等待对列中,会导致厨师做了一碗面,本应该唤醒顾客线程吃面,但是这时候有可能唤醒的是一个厨师线程,而厨师线程因为判断有没有面,因为已经有面而陷入等待(而顾客线程因为没有被唤醒而无法吃面)
pthread_cond_wait:一直阻塞等待;
pthread_cond_timewait:限时等待,等待超时后报错返回;
在我们对一些全局变量的进行非原子性操作的时候就可能出现非线程安全,比如我们吃面的问题。
我们做面的人就是生产者,吃面的人就是我们的消费者,当我们的消费者需要吃面的时候就唤醒我们的生产者进行生产,当我们有面的时候我们的生产者就不继续生产面条,去唤醒我们的消费者进行消费。
因此此时就需要考虑到生产者和消费者模型;
2.生产者,消费者模型:
- 如何保证生产者与消费者的线程安全?
- 生产者与生产者应该具有互斥关系;
- 消费者与消费者之间应该具有互斥关系;
- 生产者和消费者之间应该具有同步与互斥;
实现生产者和消费者模型:
一个场所:
一个场所就是我们多个线程能够同时操作的,比如一个全局变量的链表或者一个类中的队列等等
- 两种角色:
两种角色是我们的生产者和消费者
- 三种关系:
三种关系就是生产者与生产者、消费者与消费者、生产者和消费者时间的关系
解决问题:解耦合,支持忙闲不均,支持并发;这些优点都是通过场所来提供的,因为多个角色有可能同时操作场所,因此要保证场所的安全:
- 生产者与生产者之间应该保持互斥关系;
- 消费者与消费者之间应该保持互斥关系;
- 生产者与消费者应该保持同步+互斥关系;
代码实现:
特别指出:因为促使条件满足之后,pthread_con_singnal唤醒至少一个等待线程,导致因为条件的判断是一个if语句而造成一碗面多吃的情况,第一个吃面的人加锁吃面之后解锁,第二个吃面的人被唤醒继续吃面,此时条件的判断需要使用while,因为促使条件满足后,othrad_cond_wait唤醒是所有等待在条件变量线程,但是有可能唤醒的线程也是一个坐满的线程,因为已经有面条,条件不满足而陷入等待,导致死等,本质的原因就是唤醒了错误的角色。(因为不同的角色等待在统一条件变量上);
#include <iostream>
#include <queue>
#include <stdlib.h>
#include <unistd.h>
class Blockqueue{
public:
//构造函数,初始化我们的锁和队列的大小
Blockqueue(int cap = 10):_capcity(cap){
pthread_mutex_init(&_mutex,NULL);
pthread_cond_init(&_cond_prodoct,NULL);
pthread_cond_init(&_cond_consumer,NULL);
}
~Blockqueue(){//析构函数,销毁我们的锁和条件变量
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond_prodoct);
pthread_cond_destroy(&_cond_consumer);
}
//提供公共的接口。出栈和入栈
bool QueuePush(int data){
//加锁
QueueLock();
while(QueueIsfull()){//当队列满了生产者等待等待
ProductorWait();
}
//进行入队列的操作
_queue.push(data);
//唤醒我们的消费者
ConsumerWakeup();
//解锁
QueueUnlock();
return true;
}
bool QueuePop(int* data){
QueueLock();//加锁
//此时就是唤醒我们的队列的时候应该是while虚循环等待
while(QueueIsempty()){//当队列是空的就等待
ConsumerWait();
}
//进行出队列的操作
*data = _queue.front();
_queue.pop();
//唤醒我们的生产者
ProductorWakeup();
QueueUnlock();
return true;
}
private:
//实现上面的小函数
void QueueLock(){
pthread_mutex_lock(&_mutex);
}
void QueueUnlock(){
pthread_mutex_unlock(&_mutex);
}
void ProductorWait(){
pthread_cond_wait(&_cond_prodoct,&_mutex);
}
void ConsumerWait(){
pthread_cond_wait(&_cond_consumer,&_mutex);
}
void ProductorWakeup(){
pthread_cond_signal(&_cond_prodoct);
}
void ConsumerWakeup(){
pthread_cond_signal(&_cond_consumer);
}
bool QueueIsfull(){
return (_queue.size() == _capcity);
}
bool QueueIsempty(){
return _queue.empty();
}
private:
//一个队列
std::queue<int> _queue;
int _capcity;//容量
pthread_mutex_t _mutex;//一个锁
return _queue.empty();
}
private:
//一个队列
std::queue<int> _queue;
int _capcity;//容量
pthread_mutex_t _mutex;//一个锁
pthread_cond_t _cond_prodoct;
pthread_cond_t _cond_consumer;
};
void* thr_product(void* arg){
Blockqueue* p = (Blockqueue*)arg;
int i = 0;
while(1){
p->QueuePush(i++);
std::cout<<"生产者生产数据:"<<i<<std::endl;
}
return NULL;
}
void* thr_consumer(void* arg){
Blockqueue* p = (Blockqueue*)arg; int data;
while(1){
p->QueuePop(&data);
std::cout<<"消费者使用数据:"<<data<<std::endl;
}
private:
//一个队列
std::queue<int> _queue;
int _capcity;//容量
pthread_mutex_t _mutex;//一个锁
pthread_cond_t _cond_prodoct;
pthread_cond_t _cond_consumer;
};
void* thr_product(void* arg){
Blockqueue* p = (Blockqueue*)arg;
int i = 0;
while(1){
p->QueuePush(i++);
std::cout<<"生产者生产数据:"<<i<<std::endl;
}
return NULL;
}
void* thr_consumer(void* arg){
Blockqueue* p = (Blockqueue*)arg;
int data;
while(1){
p->QueuePop(&data);
std::cout<<"消费者使用数据:"<<data<<std::endl;
}
return NULL;
}
int main(){
pthread_t ptid[4],ctid[4];
//创建四个生产者和四个消费者线程
//创建一个对垒
Blockqueue q;
int i = 0;
int ret;
for(i = 0;i < 4; i++){
ret = pthread_create(&ptid[i],NULL,thr_product,(void*)&q);
if(ret < 0){
std::cout<<"创建线程错误"<<std::endl;
return 0;
}
}
for(i = 0;i < 4; i++){
ret = pthread_create(&ctid[i],NULL,thr_consumer,(void*)&q);
if(ret < 0){
std::cout<<"创建线程错误"<<std::endl;
return 0;
}
}
//线程回收
for(int i = 0;i < 4;i++){
pthread_join(ptid[i],NULL);
}
for(int i = 0;i < 4;i++){
pthread_join(ctid[i],NULL);
}
return 0;
}
二.信号量:
1.信号量的本质:
一个计数器—做资源计数,判断当前是否可以对临界资源进行操作)+等待对列+等待+唤醒;
2.功能:实现线程间的同步与互斥;
3.原理:
-
互斥原理: 只具有0/1计数的计数时,就可以实现互斥;
初识计数为1,1表示当前只有一个线程可以获取资源,获取资源后-1;临界资源操作完毕之后;
计数+1;并且唤醒等待对列上的线程;
计数为0,其他线程进行一个等待;(将pcb加入到等待对列) -
同步原理: 对程序逻辑进行控制(对临界资源合理操作控制);通过计数判断当前是否能够对临界资源进行操作,不能操作(计数<=0),则等待;
其他线程促使条件判断后,对计数+1,唤醒等待的线程;
4.条件变量实现同步与信号量的区别:
- 信号量并不需要搭配互斥锁的使用;
- 信号量的本身计数就是是否能够对临界资源进行操作的判断条件;
条件变量需要外部用户判断流程;
5.信号量的实现流程:
- 初始化信号量:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem:设置的信号量的变量;
pshared:设置用于线程还是进程,0用于线程间的同步与互斥,1用于进程间的同步与互斥;
value:信号量计数器的初值;
- 信号量等待:
#include <semaphore.h>
int sem_wait(sem_t *sem); //等待;
int sem_trywait(sem_t *sem); //尝试等待;
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //
//等待是计数大于0的时候wait操作之后计数减1,直到计数小于等于0的时候则阻塞等待;
参数:
sem:是我们定义的信号量变量;
abs_timeout:是一个时间结构体;
- 信号量唤醒:
#include <semaphore.h>
int sem_post(sem_t *sem);
唤醒等待操作,则计数+1;
参数:
sem:定义的信号量变量;
- 信号量销毁:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
6.信号量实现生产者消费者模型:
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>
class Blockqueue{
public:
Blockqueue(int cap = 10):_queue(10),_capacity(cap),
_read_step(0),_write_step(0)
{
//对我们三个信号量进行初始化
//int sem_init(sem_t *sem, int pshared, unsigned int
//value);
sem_init(&_sem_data,0,0);
sem_init(&_sem_idle,0,cap);
sem_init(&_sem_lock,0,1);
}
~Blockqueue(){
sem_destroy(&_sem_data);
sem_destroy(&_sem_idle);
sem_destroy(&_sem_lock);
}
//提供公共的接口
bool QueuePush_back(int data){
//生产者等待
ProductWait();
//加锁
QueueLock();
//生产数据
_queue[_write_step] = data;
_write_step = (_write_step+1)% _capacity;
//解锁
QueueUnlock();
//唤醒生产者
ConsumerWakeup();
return true;
}
bool QueuePop(int* data){
//消费者等地啊
ConsumerWait();
//加锁
QueueLock();
//消费数据
*data = _queue[_read_step];
_read_step = (_read_step+1)%_capacity;
//解锁
QueueUnlock();
//唤醒我们的生产者
ProductWakeup();
return true;
}
private:
void QueueLock(){//加锁
sem_wait(&_sem_lock);
}
void QueueUnlock(){
sem_post(&_sem_lock);
}
void ProductWakeup(){
sem_post(&_sem_idle);
}
void ProductWait(){
sem_wait(&_sem_idle);
}
//此时对于消费者来说的话我们是对于数据资源来说的
void ConsumerWait(){
sem_wait(&_sem_data);
}
void ConsumerWakeup(){
sem_post(&_sem_data);
}
private:
//一个队列
std::vector<int> _queue;
int _capacity;//容量
int _read_step;//读的位置
int _write_step;//写的位置
sem_t _sem_data;//数据资源空间
sem_t _sem_idle;//空闲资源空间
sem_t _sem_lock;//实现互斥的信号量
};
void* thr_consumer(void* arg){
Blockqueue* b = (Blockqueue*)arg;
int data;
while(1){
b->QueuePop(&data);
std::cout<<"消费者消费数据"<<data<<std::endl;
}
return NULL;
}
void* thr_productor(void* arg){
Blockqueue* b = (Blockqueue*)arg;
int i = 0;
while(1){
std::cout<<"生产者生产了数据"<<i<<std::endl;
b->QueuePush_back(i++);
}
return NULL;
}
int main(){
pthread_t ctid[4],ptid[4];
//创建四个生产者和四个消费者
Blockqueue b;
int ret = 0;
int i = 0;
for(i = 0;i < 4; i++){
ret = pthread_create(&ctid[i],NULL,thr_consumer,(void*)&b);
if(ret < 0){
std::cout<<"线程创建出现问题"<<std::endl;
return 0;
}
}
for(i = 0;i < 4; i++){
ret = pthread_create(&ptid[i],NULL,thr_productor,(void*)&b);
if(ret < 0){
std::cout<<"线程创建出现问题"<<std::endl;
return 0;
}
}
for(int i = 0;i < 4;i++){
pthread_join(ctid[i],NULL);
}
for(int i = 0;i < 4;i++){
pthread_join(ptid[i],NULL);
}
return 0;
}