Qt之线程同步(生产者消费者模式 - QSemaphore)

https://blog.csdn.net/liang19890820/article/details/52639757?spm=a2c4e.11153940.blogcont62060.18.45637046KH4bpI

简述

生产者将数据写入缓冲区,直到它到达缓冲区的末尾,此时,它将从开始位置重新启动,覆盖现有数据。消费者线程读取数据并将其写入标准错误。

Semaphore(信号量) 比 mutex(互斥量)有一个更高级的并发性。如果缓冲区的访问由一个 QMutex 把守,当生产者线程访问缓冲区时,消费者线程将无法访问。然而,有两个线程同一时间访问不同的缓冲区是没有害处的。

示例包括两个类:Producer 和 Consumer,均继承自 QThread。循环缓冲区用于这两个类之间的沟通,信号量用于保护全局变量。

另一种实现“生产者 - 消费者”模式的方案是使用 QWaitCondition 和 QMutex - Qt之线程同步(生产者消费者模式 - QWaitCondition)

版权声明:一去、二三里,未经博主允许不得转载。

全局变量

首先,一起来看循环缓冲区和相关的信号量:

const int DataSize = 100000;

const int BufferSize = 8192;
char buffer[BufferSize];

QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

DataSize 是生产者将生成的数据数量,为了让示例尽可能地简单,把它定义为一个常数。BufferSize 是循环缓冲区的大小,小于 DataSize,这意味着在某一时刻生产者将达到缓冲区的末尾,会从开始位置重新启动。

要同步生产者和消费者,需要两个信号量。freeBytes 信号量用于控制缓冲区的 "free" 区域(生产者尚未填充数据,或消费者已经读取的区域)。usedBytes 信号量用于控制缓冲区的 "used" 区域(生产者已经填充数据,但消费者尚未读取的区域)。

总之,这些信号量确保生产者不会先于消费者超过 BufferSize 的大小,而消费者永远不会读取生产者尚未生成的数据。

freeBytes 信号量用 BufferSize 来初始化,因为最初整个缓冲区是空的。usedBytes 信号量初始化为 0(默认值,如果未指定)。

Producer

Producer 类的代码如下:

class Producer : public QThread
{
public:
    void run() Q_DECL_OVERRIDE
    {
        qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
        for (int i = 0; i < DataSize; ++i) {
            freeBytes.acquire();
            buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
            usedBytes.release();
        }
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

生产者生成 DataSize 字节的数据。在往循环缓冲区写入一个字节之前,它必须使用 freeBytes 信号量来获得一个 "free"字节。如果消费者没有与生产者保持着同样的速度,QSemaphore::acquire() 调用可能会阻塞。

最后,生产者使用 usedBytes 信号量释放一个字节。该 "free" 字节已成功转化为一个 "used" 字节,准备好供消费者读取。

Consumer

现在转向 Consumer 类:

class Consumer : public QThread
{
    Q_OBJECT
public:
    void run() Q_DECL_OVERRIDE
    {
        for (int i = 0; i < DataSize; ++i) {
            usedBytes.acquire();
            fprintf(stderr, "%c", buffer[i % BufferSize]);
            freeBytes.release();
        }
        fprintf(stderr, "\n");
    }

signals:
    void stringConsumed(const QString &text);

protected:
    bool finish;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

代码非常类似于生产者,不同的是,获得 "used" 字节并释放一个 "free" 字节,而非相反。

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

当运行这个程序时,会发生什么呢?

最初,生产者是唯一一个可以做任何事情的线程,消费者阻塞并等待 usedBytes 信号量的释放(available() 初始数是 0)。一旦生产者把一个字节放入缓冲区,freeBytes.available() 就会变为 BufferSize - 1,并且 usedBytes.available() 变为 1。这时,可能发生两件事:要么消费者线程接管和读取字节,要么生产者开始生成第二个字节。

此示例中提出的“生产者 - 消费者”模式,适用于编写高并发多线程应用。在多处理器计算机中,程序可能比基于 mutex 的方案快达两倍之多,因为两个线程可以同一时间在缓冲区的不同部分处于激活状态。

要知道,这些好处并不总能实现,获取和释放一个 QSemaphore 是需要成本的。在实践中,可能需要把缓冲区分为块,并针对块操作而非单个字节。缓冲区的大小也是一个必须仔细选择的参数,需要基于实验。

更多参考


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值