我参考的网址:https://mp.weixin.qq.com/s/wh9RLbDwP1AoNy50i4WEdg
https://www.cnblogs.com/chentingk/p/6497107.html
生产者-消费者模型
生产者消费者模型的实现
生产者是一堆线程,消费者是另一堆线程,内存缓冲区可以使用List数组队列,数据类型只需要定义一个简单的类就好。关键是如何处理多线程之间的协作。这其实也是多线程通信的一个范例。
在这个模型中,最关键就是内存缓冲区为空的时候消费者必须等待,而内存缓冲区满的时候,生产者必须等待。其他时候可以是个动态平衡。值得注意的是多线程对临界区资源的操作时候必须保证在读写中只能存在一个线程,所以需要设计锁的策略。
qt中相关的库
条件变量QWaitCondition
Qt里面叫等待条件,Linux下叫条件变量,统一都称呼为条件变量QWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。
QWaitCondition ()
bool wait ( QMutex * lockedMutex, unsigned long time = ULONG_MAX )
释放lockedMutex并等待等待条件。lockedMutex最初必须由调用线程锁定。如果lockedMutex未处于锁定状态,则行为未定义。如果lockedMutex是递归互斥体,则此函数将立即返回。lockedMutex将被解锁,调用线程将阻塞,直到满足以下任一条件:
1、另一个线程使用wakeOne()或wakeAll()发出信号。在这种情况下,此函数将返回true。
2、time毫秒已过。如果time是ULONG_MAX(默认值),那么等待永远不会超时(必须用信号通知事件)。如果等待超时,此函数将返回false。
lockedMutex将返回到相同的锁定状态。提供此函数是为了允许原子从锁定状态转换到等待状态。
bool wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX )
释放lockedReadWriteLock并等待等待条件。lockedReadWriteLock最初必须由调用线程锁定。如果lockedReadWriteLock未处于锁定状态,则此函数将立即返回。lockedReadWriteLock不能递归锁定,否则此函数将无法正确释放锁。lockedReadWriteLock将被解锁,调用线程将阻塞,直到满足以下任一条件:
1、另一个线程使用wakeOne()或wakeAll()发出信号。在这种情况下,此函数将返回true。
2、time毫秒已过。如果time是ULONG_MAX(默认值),那么等待永远不会超时(必须用信号通知事件)。如果等待超时,此函数将返回false。
lockedReadWriteLock将返回到相同的锁定状态。提供此函数是为了允许原子从锁定状态转换到等待状态。
void wakeOne ()
唤醒一个等待条件的线程。被唤醒的线程取决于操作系统的调度策略,无法控制或预测。
如果要唤醒特定线程,解决方案通常是使用不同的等待条件,并让不同的线程在不同的条件下等待。
void wakeAll ()
唤醒等待条件的所有线程。线程的唤醒顺序取决于操作系统的调度策略,无法控制或预测。
头文件声明: #include
等待条件声明: QWaitCondtion m_WaitCondition;
等待条件等待: m_WaitConditon.wait(&m_muxtex, time);
等待条件唤醒: m_WaitCondition.wakeAll();
在经典的生产者-消费者场合中,生产者首先必须检查缓冲是否已满,如果缓冲区已满,线程停下来等待 notfull条件。如果没有满,在缓冲中生产数据,激活条件 notempty。使用mutex来保护对buffer的访问。QWaitCondition::wait() 接收一个mutex作为参数,mutex被调用线程初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒时,mutex会处于锁定状态,从锁定状态到等待状态的转换是原子操作。当程序开始运行时,只有生产者可以工作,消费者被阻塞等待notempty条件,一旦生产者在缓冲中放入一个字节,notempty条件被激发,消费者线程于是被唤醒。
notfull和notempty是自定义的等待条件
内存缓冲区,虽然buffer有大小限制,但实现的是循环的
class producons
{
public:
int buffer[BUFFER_SIZE]; /*数据*/
QMutex lock; //互斥锁
int readpos,writepos; //读写位置
QWaitCondition notempty; //条件变量 没有空间
QWaitCondition notfull; //条件变量 没有货物
producons()
{
readpos = writepos = 0;
}
};
producons buffer; //内存缓冲区对象
生产者线程,只修改writepos
class Producor:public QThread
{
public:
void run();
void put(producons * prod,int data);
};
void Producor::run()
{
int n;
for(n = 0;n<5;n++)
{
qDebug()<<"生产者睡眠 1s...";
sleep(1);
qDebug()<<"生产信息:" << n;
put(&buffer, n);
}
for(n=5; n<10; n++)
{
qDebug()<<"生产者睡眠 3s...";
sleep(3);
qDebug()<<"生产信息:"<< n;
put(&buffer,n);
}
put(&buffer, -1);
qDebug()<<"结束生产者!\n";
return;
}
void Producor::put(producons *prod, int data)
{
prod->lock.lock();
//这句话相当于生产者不断的生产,writepos就是生产者当前正在生产的位置,readpos就是最后一个消费者,现在就是之前生产的还没消耗,writepos跟在readpos后面,buffer是一个循环的缓冲区
while((prod->writepos + 1)%BUFFER_SIZE == prod->readpos)
{
qDebug()<<"生产者等待生产,直到buffer有空位置";
prod->notfull.wait(&prod->lock);
}
//将数据写入到buffer里面去
prod->buffer[prod->writepos] = data;
prod->writepos++;
if(prod->writepos >= BUFFER_SIZE)
prod->writepos = 0;
//仓库已满,唤醒所有线程,等待消费者消费,该线程被阻塞
prod->notempty.wakeAll();
prod->lock.unlock();
}
消费者线程,只修改readpos
class Consumer:public QThread
{
public:
void run();
int get(producons *prod);
};
void Consumer::run()
{
int d = 0;
while(1)
{
qDebug()<<"消费者睡眠 2s...";
sleep(2);
d = get(&buffer);
qDebug()<<"读取信息:"<< d;
if(d == -1) break;
}
qDebug()<<"结束消费者!";
return;
}
int Consumer::get(producons *prod)
{
int data;
prod->lock.lock(); //加锁
//此时表示buffer的这个位置既要读取,又要写入,即消费者没有东西可以消费,需等待生产者生产
while(prod->writepos == prod->readpos)
{
qDebug()<<"消费者等待,直到buffer有消息\n";
prod->notempty.wait(&prod->lock);
}
//读取buffer里面的消息
data = prod->buffer[prod->readpos];
prod->readpos++;
if(prod->readpos >=BUFFER_SIZE)
prod->readpos = 0;
//触发非满条件变量 告诉生产者可以生产
prod->notfull.wakeAll();
prod->lock.unlock();
return data;
}