线程安全(条件变量)

一、什么是同步

对于多线程编程而言,多个线程按照某种规则访问临界资源,避免线程产生饥饿问题,实现临界资源访问的合理性,这就是同步!

二、 条件变量

2.1 如何实现同步

条件变量本质提供了一个pcb等待队列和使线程阻塞,以及唤醒线程的接口,但是什么时候去唤醒,什么时候阻塞,需要用户自己控制合理的时序逻辑(在合适的时候调用条件变量合适的接口)。

2.1 相关接口

定义条件变量变量:pthread_cond_t * cond
条件变量的初始化:int pthread_cond_init(pthread_cond_t * restrict cond,const pthread_condattr_t * restrict attr);
条件变量的销毁:int pthread_cond_destroy(pthread_cond_t * cond);
在这里插入图片描述
1.阻塞线程(前提是线程不满足访问资源的条件):int pthread_cond_wait(pthread_cond_t * restrict cond,pthread_mutex_t * restrict mutex);
1.阻塞线程(前提是线程不满足访问资源的条件或者没有到达特定时间): int pthread_cond_timedwait(pthread_cond_t * restrict cond,pthread_mutex_t * restrict mutex,const struct timespec * restrict abstime);
在这里插入图片描述
唤醒阻塞的线程:
int pthread_cond_signal(pthread_cond_t * cond);–唤醒至少一个线程
int pthread_cond_broadcast(pthread_cond_t * cond);–唤醒全部线程

在这里插入图片描述
举例:
对于同步,我们举一个这样的例子:厨师和顾客,一个顾客去饭馆吃饭,他就会给厨师说,来一碗盖浇饭,并且在餐馆等待,这是厨师就会去做一碗盖浇饭,反之如果有饭,则厨师等待,等待顾客需要饭时,再做饭,这样一个循环的过程。
首先我们只讨论一个厨师和一个顾客的问题
在这里插入图片描述
首先我们再条件变量相关接口中需要用到条件变量以及互斥锁,所以我们定义了这两个变量,并且初始化它们,在线程退出后去摧毁它们。
关于顾客吃盖浇饭还有厨师做盖浇饭的当然就在2个线程的入口函数中去实现了,如图:
在这里插入图片描述
运行结果
在这里插入图片描述
这样就是一个同步的过程。
这里我们定义互斥锁的原因是:
1.多线程之间的资源访问时非原子操作,所以在做饭或者吃饭时,需要加锁。
2.接口pthread_cond_wait中用到了互斥锁。
这里需要注意的就是,在我写的代码中可以看出,多线程之间的资源访问时非原子操作,那我并没有在顾客等待或者厨师等待之前进行解锁,原因是:
1.举个例子,如果在顾客休眠前进行了解锁后,厨师做了一碗盖浇饭,去唤醒顾客,但是顾客这是并没有进行加锁,就吃饭,吃完之后解锁,就会造成程序紊乱。
2.我们发现pthread_cond_wait接口中用到了互斥锁,是因为他自带了互斥锁的功能,在睡眠前解锁,在唤醒后加锁。

这就是条件变量实现的同步的功能,有饭才能吃,有顾客才做饭,让多个线程按照某种规则访问临界资源,也就是这个碗!!(从碗里吃饭,将盖浇饭做到碗里)
这时我们增加顾客和厨师的数量,如图:
在这里插入图片描述
其他的入口函数不变的情况下,我们会发现,当程序运行时会出现如图所示情况:
在这里插入图片描述
卡在了这个地方。
原因如下:
使用一个条件变量只有一个PCB的阻塞队列,而当3个顾客线程进入对应线程入口函数后,没有饭就会被挂在阻塞队列上,这时会唤醒一个厨师做饭,做好饭后,厨师线程就会被挂在3个顾客线程的后面,同时一个顾客线程被唤醒后出盖浇饭,而其他的顾客在没饭情况下,唤醒厨师线程时,由于只有一个队列,队列是先进先出的,所以顾客进程就唤醒了一个顾客进程,但是顾客不会做饭,所以就陷入了死循环。如图:
在这里插入图片描述
所以在这我们就需要去创见两个条件变量,让顾客阻塞在顾客PCB的阻塞队列,厨师阻塞在厨师的PCB的阻塞队列,唤醒时厨师唤醒顾客,顾客唤醒厨师,分工明确就可以了(代码的实现大家可以自行完成,只需添加一个条件变量即可)。

2.3 生产与消费者模型

2.3.1 生产与消费者模型的基本认识

生产与消费者模型是一个非常典型的设计模式
设计模式:用户针对典型的应用场景,所设计的对应的解决方案
应用场景:大量数据产生并且进行数据处理的场景。
优点
1.解耦合(生产者只考虑数据接收,消费者是考虑数据处理)
2.支持忙闲不均(数据过多,将数据放入缓冲区,从而慢慢进行数据处理)
3.支持并发处理(当数据过多,可以增加数据处理的线程,提高数据处理的效率)。

2.3.2 生产与消费者模型的实现(阻塞队列)

这个模型的实现,其实跟上边我们举例中的多顾客多厨师类似,不同的线程在不同的阻塞队列中,唤醒后从同一个缓冲区处理数据(消费者)或者将数据放置缓冲区(生产者)。
具体实现如下:

#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;
class BlockQueue{
private:
	queue<int> _queue;    //队列
	pthread_mutex_t _mutex;    //互斥锁
	pthread_cond_t _cond_cus;   //消费者条件变量
	pthread_cond_t _cond_pro;   //生产者条件变量
	size_t _capacity;       //队列容量
public:
	//初始化(互斥锁以及条件变量的初始化)
	BlockQueue(size_t cap)        
		:_capacity(cap)
	{
		pthread_mutex_init(&_mutex, NULL);
		pthread_cond_init(&_cond_cus, NULL);
		pthread_cond_init(&_cond_pro, NULL);
	}
	//入队(整型数据入队)
	bool push(const size_t data){
		pthread_mutex_lock(&_mutex);
		while (_capacity == _queue.size()){
			pthread_cond_wait(&_cond_pro, &_mutex);
		}
		_queue.push(data);
		pthread_mutex_unlock(&_mutex);
		pthread_cond_broadcast(&_cond_cus);
		return true;
	}
	//出队(整型数据出队并且获取出队的数据)
	bool pop(int *buf){
		pthread_mutex_lock(&_mutex);
		while (_queue.empty()){
			pthread_cond_wait(&_cond_cus, &_mutex);
		}
		*buf = _queue.front();
		_queue.pop();
		pthread_mutex_unlock(&_mutex);
		pthread_cond_broadcast(&_cond_pro);
		return true;
	}
	//销毁(互斥锁以及条件变量的销毁)
	~BlockQueue(){
		pthread_mutex_destroy(&_mutex);
		pthread_cond_destroy(&_cond_cus);
		pthread_cond_destroy(&_cond_pro);
	}
};

具体演示如下:
在这里插入图片描述
这里我们会发现队列容量是5,但是有时候连续入队数据以及出对的数据多余5或者少于5,这就是由于入队或者出队和printf函数是一个非原子操作,并且由于时间片的转换导致的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值