条件变量的封装:
Cond:lesson34
cpp
#pragma once
#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"
using namespace MutexModule;
namespace CondModule
{
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, nullptr);
}
void Wait(Mutex &mutex)
{
int n = pthread_cond_wait(&_cond, mutex.Get());//从Mutex里面拿到锁
(void)n;
}
void Signal()
{
// 唤醒在条件变量下等待的一个线程
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Broadcast()
{
// 唤醒所有在条件变量下等待的线程
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
};
1.回顾一下相关概念

POSIX信号量是一个标准,和System V是一样的。
信号量概念:信号量本质是一个计数器,是对特定资源的预定机制。----->申请信号量成功,预定资源。
多线程使用资源的两种场景:
1.将目标资源整体使用【使用锁技术 / 2元信号量】,计数器为1的信号量:2元信号量
2.将目标资源按照不同的"块",分批使用【信号量】,如多生产多消费者模型
把目标资源划分成一块一块的小资源,就可以并发地放多个线程来并发地进行访问。 - 采用信号量技术
申请成果 - 资源地预定。
"注意不要访问同一个位置,不要放入过多线程进来。"
所有线程,都先看到sem(信号量),计数器,sem--,sem++信号量本质也是临界资源
操作:PV操作
P: -- ,原子的,资源--
V:++,原子的,资源++
2.基于信号量设置生产者消费者模型&&环形队列的身缠这消费者模型
环形队列:

怎么判断环形队列是空的还是满的???
空和满的判断标准
(1)环形队列为空,head = tail
(2)为满,head = tail
解决方案:
1.定义一个计数器
2.空一格位置,head = tail --->空
判断tail的下一个位置是不是等于head--->证明队列已满。
怎么实现环形队列???
定义一个长度为N的数组,主要 %=,取模操作,满了就取模归0
举个例子

题意背景
大圆桌,上空格->盘子。
生产者放苹果,消费者拿苹果
约定:
约定1:空,生产者先运行
约定2:满,消费者先运行
约定3:生产者不能消费者套一个圈即以上
约定4:消费者不能超过生产者
思考:
1.只要我们不访问同一个位置,我们就可以同时进行
2.什么时候,我们会在同一个位置???
只有为空为满时,才会访问同一个位置。
为空:【互斥】,生产者【同步】先运行
为满:【互斥】,消费者【同步】先运行
结论:
结论1:环形队列,不为空,或者&&不为满,生产消费可以同时进行!
结论2:环形队列,为空 || 未满,生产消费需要同步互斥!
如何保证4个约定???----采用信号量保证!!
初始状态下:
生产者:
资源:环形队列中空地位置
初始sem_blank = N
消费者:
资源:环形队列中地数据
初始sem_data = 0
伪代码实现


伪代码操作,满足4个约定
信号量P原子,申请成功,继续运行
申请失败,申请的线程会被阻塞
3.熟悉接口
初始化信号量
cpp
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表⽰线程间共享,⾮零表⽰进程间共享
value:信号量初始值
销毁信号量
cpp
1 int sem_destroy(sem_t *sem);
等待信号量
cpp
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
发布信号量
cpp
功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
4.编写代码 ---多生产多消费
Sem.hpp
cpp
#pragma once
#include<iostream>
#include<pthread.h>
#include <semaphore.h>
//信号量必须包含这个头文件
namespace SemModule{
const int defaultvalue = 1;
class Sem{
public:
//初始化
Sem(unsigned int value = defaultvalue)//表示信号量的初始值
{
sem_init(&_sem,0,value);
}
//P操作
void P()
{
sem_wait(&_sem);
}
//V操作
void V()
{
sem_post(&_sem);
}
//灭掉
~Sem()
{
sem_destroy(&_sem);
}
private:
sem_t _sem;
};
}
RingQueue.hpp环形队列
cpp
#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include"Mutex.hpp"
#include"Sem.hpp"
using namespace std;
using namespace MutexModule;
using namespace SemModule;
template<typename T>
class RingQueue{
public:
RingQueue(int cap = 5)
:_cap(cap)
,_rq(cap)//设置环形队列的空间大小
,_blank_sem(cap)//生产者的信号量大小
,_p_loc(0)
,_data_sem(0)
,_c_loc(0)//初始化从下到上
{}
void Equeue(const T& in)
{
//先分配资源,然后在竞争锁,这样的效率更高
_blank_sem.P();
{
LockGuard lock(_pmutex);
_rq[_p_loc] = in;
_p_loc++;
_p_loc%=_cap;
}
_data_sem.V();
}
void Pop(T*out)
{
_data_sem.P();
{
LockGuard lockguard(_cmutex);
*out = _rq[_c_loc];
_c_loc++;
_c_loc%=_cap;
}
_blank_sem.V();
}
~RingQueue(){}
private:
vector<T> _rq;//环形队列
int _cap;//空间大小
//生产者
Sem _blank_sem;//空的数量
int _p_loc;//生产者的下标
//消费者
Sem _data_sem;//有数据的数量
int _c_loc;
Mutex _pmutex;//生产锁
Mutex _cmutex;//消费锁
};
Mutex.hpp
cpp
#pragma once
#include<iostream>
#include<pthread.h>
namespace MutexModule{
class Mutex{
public:
Mutex(){
pthread_mutex_init(&_mutex,nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
}
pthread_mutex_t* Get()
{
return &_mutex;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex;
};
class LockGuard{
public:
LockGuard(Mutex& mutex)
:_mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex _mutex;
};
}
Main.cc
cpp
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include"RingQueue.hpp"
#include<cstring>
struct threaddata
{
RingQueue<int>* rq;
string name;
};
void* product(void*args)
{
int data = 0;
threaddata* ss = static_cast<threaddata*>(args);
while(true)
{
sleep(2);
ss->rq->Equeue(data);
cout<<ss->name<<"生产一个:"<<data<<endl;
data++;
}
}
void* consume(void*args)
{
threaddata* ss = static_cast<threaddata*>(args);
while(true)
{
sleep(3);
int t = 0;
ss->rq->Pop(&t);
cout<<ss->name<<"消费了一个"<<t<<endl;
}
}
int main()
{
RingQueue<int>* rq = new RingQueue<int>();
pthread_t p[3],c[2];
threaddata* p1 = new threaddata();
p1->rq = rq;
p1->name = "p-thread-1";
pthread_create(p,nullptr,product,p1);
threaddata* p2 = new threaddata();
p2->rq = rq;
p2->name = "p-thread-2";
pthread_create(p+1,nullptr,product,p2);
threaddata* p3 = new threaddata();
p3->rq = rq;
p3->name = "p-thread-3";
pthread_create(p+2,nullptr,product,p3);
threaddata* c1 = new threaddata();
c1->rq = rq;
c1->name = "c-thread-1";
pthread_create(c,nullptr,consume,c1);
threaddata* c2 = new threaddata();
c2->rq = rq;
c2->name = "c-thread-2";
pthread_create(c+1,nullptr,consume,c2);
pthread_join(p[0], nullptr);
pthread_join(p[1], nullptr);
pthread_join(p[2], nullptr);
pthread_join(c[0], nullptr);
pthread_join(c[1], nullptr);
return 0;
}
Makefile
cpp
ring:Main.cc
g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f ring
问题:单生产单消费是通过给整体上锁来实现的,那么多生产多消费呢????
cpp
void Equeue(const T& in)
{
//先分配资源,然后在竞争锁,这样的效率更高
_blank_sem.P();
{
LockGuard lock(_pmutex);
_rq[_p_loc] = in;
_p_loc++;
_p_loc%=_cap;
}
_data_sem.V();
}
void Pop(T*out)
{
_data_sem.P();
{
LockGuard lockguard(_cmutex);
*out = _rq[_c_loc];
_c_loc++;
_c_loc%=_cap;
}
_blank_sem.V();
}
是通过定义两个锁,来实现的,满足4个约定。有信号量,上。无信号量,等。
先分配锁还是先分配资源???
先划分资源,在申请锁--->效率高。先买票,再排队
先加锁,才可以申请资源,--->效率低,先排队再买票
结论:先分配资源,然后再竞争锁,不然会导致效率低。
