1. 什么是 QWaitCondition
QWaitCondition
也是用于多线程同步的。线程 A 调用 QWaitCondition::wait()
函数后,处于等待阻塞状态;线程 B 调用 QWaitCondition::wake()
函数后 唤醒 线程 A,线程 A 就会继续执行。
下面再通过一个例子理解一下:线程 A 通过调用 send()
函数,发送一个数据包给 线程 B ,此时线程 A 处于阻塞等待状态,直到线程 B 接收到这个数据包并将其处理完毕之后,再将其传回线程 A 并且 再唤醒线程 A 。
2. 示例
// 线程 A
send(&packet);
mutex.lock();
condition.wait(&mutex);
if (m_receivedPacket)
{
handle_packet(m_receivedPacket); // 线程B传回来的包
}
mutex.unlock();
// 线程 B
m_receivedPacket = parse_packet(buffer); // 将接收的数据解析成包
condition.wakeAll();
以上的示例代码是存在一个问题的。
如果 线程 A 调用 send()
函数后,线程 B 立马将解析完的数据包返回 并且 唤醒线程 A,此时线程 A 还没来得及调用 condition.wait()
函数。那么很明显,线程 B 的唤醒是无效的,等到 线程 A 真正调用condition.wait()
函数的时候,就会一直阻塞在这里。
3. 解决方法
首先我们要保证这样的一个顺序:线程 A 发送数据包给线程 B —> 线程 A 进入等待阻塞状态 —> 线程 B 将解析完的数据包发回线程 A —> 线程 B 唤醒 线程 A —> 线程 A 再完成剩下的操作。
我们参考 Qt
官方示例:
forever {
mutex.lock();
keyPressed.wait(&mutex);
do_something();
mutex.unlock();
}
forever {
getchar();
keyPressed.wakeAll();
}
将之前的示例稍作修改:
// 修改后的示例
// 线程 A
mutex.lock();
send(&packet);
condition.wait(&mutex);
if (m_receivedPacket)
{
handle_Ppacket(m_receivedPacket); // 线程B传回来的包
}
mutex.unlock();
// 线程 B
m_receivedPacket = parse_packet(buffer); // 将接收的数据解析成包
mutex.lock();
condition.wakeAll();
mutex.unlock();
修改后的上述示例代码,线程 A 先获取 mutex
锁,即从发送数据包开始,一直到 QWaitCondition::wait()
在操作系统层次真正执行阻塞等待指令,这一段线程 A 的时间段内,mutex
一直被上锁,即使 线程 B 很快就接收到数据包,也不会直接调用 wakeAll()
,而是在调用 mutex.lock()
时阻塞住(因为 线程 A 已经获取mutex
锁了,再尝试上锁就会被阻塞),直到 线程 A QWaitCondition::wait()
真正执行操作系统的阻塞等待指令并释放mutex
,线程 B 的 mutex.lock()
才即出阻塞,继续往下执行,调用 wakeAll()
,此时一定能唤醒 线程 A 成功。
这样我们就保证了 先调用 wait()
,再调用 wake()
。
大致的流程:
- 线程 A
mutex.lock()
获得锁,上锁; - 线程 A
wait()
成功后释放锁,进入阻塞状态(OS保证wait_block
阻塞的瞬间释放锁); - 线程 B
mutex.lock()
获得锁,上锁; - 线程 B 调用
condition.wakeAll()
,OS的进程调度器尝试唤醒关联的所有线程,所以主线程进入唤醒状态,并尝试获得mutex
锁。此时mutex
已被 线程B 获得,所以 线程 A 肯定无法获得mutex
锁,进程调度器决定让主线程继续阻塞。注意,区别于之前的wait_block
调用阻塞,这次阻塞是上锁阻塞); - 线程 A
mutex.unlock()
释放锁,进程调度器尝试唤醒这个锁关联的某一个线程; - 线程 A 作为
mutex
唯一关联的线程,获得了锁mutex.lock()
,wait()
终于返回; - 线程 A 处理完的业务逻辑,调用
mutex.unlock()
释放锁。
4. 用 QWaitCondition
实现 生产者 / 消费者模型
(1) 头文件、和一些变量
#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <stdio.h>
const int dataSize = 1000;
const int bufferSize = 100;
int buffer[bufferSize];
QWaitCondition buffer_empty;
QWaitCondition buffer_full;
//用互斥量 保证线程的原子操作
QMutex mutex;
//缓冲区可用空间
int available_space = 0;
//index 指向要读取的缓冲区位置
int index = 0;
(2) 生产者线程
//生产者
class Producer : public QThread{
public:
Producer();
void run() override;
};
Producer::Producer(){}
void Producer::run(){
for(int i = 0;i < dataSize;i++){
mutex.lock();
//缓冲区是否已经 被 生产者生产的数据填满
//如果已经填满,则等待 “缓冲区有空闲空间” 条件 (buffer_empty) 成立
if(available_space == bufferSize) buffer_empty.wait(&mutex);
//若缓冲区未被填满,则向其写入一个整数值
buffer[i % bufferSize] = available_space;
//填充空间数+1
available_space++;
//唤醒 等待“缓冲区数据已经填满” (buffer_full 条件) 为 “真” 的线程
buffer_full.wakeAll();
mutex.unlock();
}
}
(3) 消费者线程
//消费者
class Consumer : public QThread{
public:
Consumer();
void run() override;
};
Consumer::Consumer(){}
void Consumer::run(){
forever{
mutex.lock();
if(available_space == 0) buffer_full.wait(&mutex);
printf("%ul::[%d] = %d\n",currentThreadId(),index,buffer[index]);
index = (index + 1) % bufferSize;
--available_space;
//唤醒等待 “缓冲区有空位” (buffer_empty 变量)条件的生产者线程
buffer_empty.wakeAll();
mutex.unlock();
}
printf("\n");
}
(4) 启动一个生产者线程 和 两个消费者线程
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer producer;
Consumer c1 , c2;
producer.start();
c1.start();
c2.start();
producer.wait();
c1.wait();
c2.wait();
return a.exec();
}
1. 完整代码
#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <stdio.h>
const int dataSize = 1000;
const int bufferSize = 100;
int buffer[bufferSize];
QWaitCondition buffer_empty;
QWaitCondition buffer_full;
//用互斥量 保证线程的原子操作
QMutex mutex;
//缓冲区可用空间
int available_space = 0;
//index 指向要读取的缓冲区位置
int index = 0;
//生产者
class Producer : public QThread{
public:
Producer();
void run() override;
};
Producer::Producer(){}
void Producer::run(){
for(int i = 0;i < dataSize;i++){
mutex.lock();
//缓冲区是否已经 被 生产者生产的数据填满
//如果已经填满,则等待 “缓冲区有空闲空间” 条件 (buffer_empty) 成立
if(available_space == bufferSize) buffer_empty.wait(&mutex);
//若缓冲区未被填满,则向其写入一个整数值
buffer[i % bufferSize] = available_space;
//填充空间数+1
available_space++;
//唤醒 等待“缓冲区数据已经填满” (buffer_full 条件) 为 “真” 的线程
buffer_full.wakeAll();
mutex.unlock();
}
}
//消费者
class Consumer : public QThread{
public:
Consumer();
void run() override;
};
Consumer::Consumer(){}
void Consumer::run(){
forever{
mutex.lock();
if(available_space == 0) buffer_full.wait(&mutex);
printf("%ul::[%d] = %d\n",currentThreadId(),index,buffer[index]);
index = (index + 1) % bufferSize;
--available_space;
//唤醒等待 “缓冲区有空位” (buffer_empty 变量)条件的生产者线程
buffer_empty.wakeAll();
mutex.unlock();
}
printf("\n");
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer producer;
Consumer c1 , c2;
producer.start();
c1.start();
c2.start();
producer.wait();
c1.wait();
c2.wait();
return a.exec();
}