Qt之线程同步(生产者消费者模式 - QWaitCondition)
来源:互联网 发布:免费三级域名申请 编辑:程序博客网 时间:2024/06/07 20:28
简述
生产者将数据写入缓冲区,直到它到达缓冲区的末尾,这时,它从开始位置重新启动,覆盖现有数据。消费者线程读取数据并将其写入标准错误。
Wait condition(等待条件)比单独使用 mutex(互斥量)有一个更高级的并发性,如果缓冲区的访问由一个 QMutex 把守,当生产者线程访问缓冲区时,消费者线程将无法访问。然而,两个线程同时访问不同的缓冲区是没有害处的。
示例包含两个类:Producer 和 Consumer,均继承自 QThread。循环缓冲区用于两个类之间的沟通,同步工具用于保护全局变量。
另一种“生产者 - 消费者”模式的方案是使用 QSemaphore - Qt之线程同步(生产者消费者模式 - QSemaphore)
- 简述
- 全局变量
- Producer
- Consumer
- main 函数
- 更多参考
全局变量
首先,一起来看循环缓冲区和相关的同步工具:
const int DataSize = 100000;const int BufferSize = 8192;char buffer[BufferSize];QWaitCondition bufferNotEmpty;QWaitCondition bufferNotFull;QMutex mutex;int numUsedBytes = 0;
DataSize 是生产者将生成的数据数量,为了让示例尽可能地简单,把它定义为一个常数。BufferSize 是循环缓冲区的大小,小于 DataSize,这意味着在某一时刻生产者将达到缓冲区的末尾,并从开始位置重新启动。
要同步生产者和消费者,需要两个 wait 条件和一个 mutex。当生产者生成一些数据时,bufferNotEmpty 条件被发射,告诉消费者可以读取它了;当消费者读取一些数据时,bufferNotFull 条件被发射,告诉生产者生成更多的数据。numUsedBytes 为缓冲区中所包含数据的字节数。
总之,wait 条件、mutex、和 numUsedBytes 计数器确保生产者不会先于消费者超过 BufferSize 的大小,而消费者永远不会读取生产者尚未生成的数据。
Producer
Producer 类的代码如下:
class Producer : public QThread{public: Producer(QObject *parent = NULL) : QThread(parent) { } void run() Q_DECL_OVERRIDE { qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == BufferSize) bufferNotFull.wait(&mutex); mutex.unlock(); buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4]; mutex.lock(); ++numUsedBytes; bufferNotEmpty.wakeAll(); mutex.unlock(); } }};
生产者生成 DataSize 字节的数据。在往循环缓冲区写入一个字节之前,它必须先检测缓冲区是否已满(例如,numUsedBytes 等于 BufferSize),如果缓冲区满了,线程就会在 bufferNotFull 条件上等待。
最后,生产者增加 numUsedBytes,并且标志 bufferNotEmpty 条件为 true,从而 numUsedBytes 必然大于 0,
我们使用一个 mutex 保护 numUsedBytes 变量的所有访问。此外,QWaitCondition::wait() 函数接受一个 mutex 作为其参数。当线程被置于休眠状态之前,该 mutex 被解锁;当线程被唤醒,该 mutex 被锁定。此外,为了防止竞争条件发生,从锁定状态到等待状态的过渡具有原子性。
Consumer
现在转向 Consumer 类:
class Consumer : public QThread{ Q_OBJECTpublic: Consumer(QObject *parent = NULL) : QThread(parent) { } void run() Q_DECL_OVERRIDE { for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == 0) bufferNotEmpty.wait(&mutex); mutex.unlock(); fprintf(stderr, "%c", buffer[i % BufferSize]); mutex.lock(); --numUsedBytes; bufferNotFull.wakeAll(); mutex.unlock(); } fprintf(stderr, "\n"); }signals: void stringConsumed(const QString &text);};
代码非常类似于生产者,在读取字节之前,需要先检查缓冲区是否为空(numUsedBytes 为 0),而非它是否为已满。并且,当它为空时,等待 bufferNotEmpty 条件。在读取字节后,减小 numUsedBytes (而非增加),并标志 bufferNotFull 条件(而非 bufferNotEmpty 条件)。
main() 函数
在 main() 函数中,我们创建两个线程,并调用 QThread::wait(),以确保在退出之前,这两个线程有时间完成。
int main(int argc, char *argv[]){ QCoreApplication app(argc, argv); Producer producer; Consumer consumer; producer.start(); consumer.start(); producer.wait(); consumer.wait(); return 0;}
当运行这个程序时,会发生什么呢?
最初,生产者是唯一一个可以做任何事情的线程,消费者阻塞并等待 bufferNotEmpty 条件被发射(numUsedBytes 是 0)。一旦生产者把一个字节放入缓冲区,numUsedBytes 就会变为 BufferSize - 1,并且 bufferNotEmpty 条件被发射。这时,可能发生两件事:要么消费者线程接管和读取字节,要么生产者开始生成第二个字节。
此示例中提出的“生产者 - 消费者”模式,适用于编写高并发多线程应用。在多处理器计算机中,程序可能比基于 mutex 的方案快达两倍之多,因为两个线程可以同一时间在缓冲区的不同部分处于激活状态。
要知道,这些好处并不总能实现,加锁和解锁一个 QMutex 是需要成本的。在实践中,可能需要把缓冲区分为块,并针对块操作而非单个字节。缓冲区的大小也是一个必须仔细选择的参数,需要基于实验。
更多参考
- Qt之线程同步(生产者消费者模式 - QSemaphore)
- Qt之线程同步(生产者消费者模式 - QWaitCondition)
- Qt之线程同步(生产者消费者模式
- Qt之线程同步(生产者消费者模式
- Qt之线程同步(生产者消费者模式 - QSemaphore)
- Qt 线程同步之 QWaitCondition
- 生产者消费者模式(同步线程)
- 线程同步--生产者消费者模式
- Qt 线程同步(QMutex、QWaitCondition、QSemaphore)
- Qt生产者消费者实验:等待条件QWaitCondition
- Qt生产者消费者实验:等待条件QWaitCondition
- 线程同步之生产者消费者
- QT之使用 QWaitCondition 同步线程小例子
- 生产者消费者模式的线程同步问题
- 同步线程--生产者与消费者模式
- 线程同步中的“生产者和消费者”模式
- 线程同步–生产者与消费者模式
- 线程同步之经典生产者-消费者模型
- 线程同步之生产者-消费者问题
- 金旭亮老师网易MOOC课程目录
- 项目经理如何才能快速成长?
- 知道这 20 个正则表达式,能让你少写 1,000 行代码
- openstack AZ
- ZooKeeper架构设计及其应用要点
- Qt之线程同步(生产者消费者模式 - QWaitCondition)
- mac下解决mysql出现中文乱码问题
- nginx详解
- Maximum call stack size exceeded
- 大数据:巨型公司用户凭据疯狂外泄,娱乐业与技术圈首当其冲
- 像素、分辨率与图片大小之关系
- OC编程中通知的使用2(监听)
- Java中的static关键字解析
- 基于LeanTween的封装