线程的互斥和同步(6)- Qt的信号量QSemaphore

上一篇文章主要介绍了使用Windows API创建和使用信号量Semaphore
线程的互斥和同步(5)- Windows的信号量Semaphore

本篇文章只要介绍Qt的信号量 QSemaphore 类的使用,并用一个 生产者-消费者 实例来说明信号量是如何提高多线程的效率的。


下面是使用类 QSemaphore 常用的函数:

  • void acquire (int n = 1); 表示请求n个资源,如果没有任何可用资源,则阻塞。
  • void release (int n = 1); 表示释放n个资源。
  • bool tryAcquire (int n = 1); 表示尝试请求n个资源,如果没有可用资源则会直接返回。也可以加上等待时间。
  • int available () const;获取当前可用资源的个数。

下面是一个 生产者-消费者 的使用示例说明:

在这个示例中,生产者向数组中写入数据;消费者从数组中读出数据,因为他们同时操作的不是同一块内存,因此同一时刻可以完全的并发执行。下面是一个示例图:

图画的不太好看,因为写入和读取的不是用一块内存,因此可以同时进行。
当数组缓存满了的时候,写入线程阻塞;当可用资源为空的时候,读取线程等待资源写入完成。

数据定义的代码如下:

#include <QSemaphore>
#include <atomic>

extern "C" int g_data[20];				// 数据缓存
extern "C" QSemaphore g_semaphore;		// 信号量
extern "C" std::atomic<int> g_nStartIndex;	// 读取数据的缓存起始位置
extern "C" std::atomic<int> g_nEndIndex;	// 写入数据的缓存起始位置
extern "C" std::atomic<int> g_nCurrentCreated;	// 当前缓存可用数目

这里定义了20个int类型的缓存数据。
全局变量的初始化,在cpp文件中

int g_data[20] = {-1};
QSemaphore g_semaphore(20);
std::atomic<int> g_nStartIndex(0);
std::atomic<int> g_nEndIndex(0);
std::atomic<int> g_nCurrentCreated(0);

生产者产生数据
生产者定义:

// 生产者
class ProducerThread : public CThread
{
public:
    void run(void) override;

private:
    int number = 0;
};

run函数为线程的入口函数,CThread 是自定义线程类

生产者的实现:

void ProducerThread::run(void)
{
    while (1)
    {
        // 请求一个资源
        g_semaphore.acquire(1);

        // 生产数据
        g_data[g_nEndIndex] = number;
        std::cout << "Created Data: " << number++ \
                  << ", Index is " << g_nEndIndex << std::endl;

        g_nEndIndex++;
        if (g_nEndIndex == 20)
            g_nEndIndex = 0;

        ::Sleep(100);
        g_nCurrentCreated++;
    }
}

生产者每个100ms生产一个数据。当缓存满了的时候,
g_semaphore.acquire(1); 语句会处于阻塞的状态,直到有资源被释放。

消费者读取数据
消费者定义:

// 消费者
class ConsumerThread : public CThread
{
public:
    void run(void) override;
};

消费者实现:

void ConsumerThread::run(void)
{
    while (1)
    {
        // 自旋等待
        while (g_nCurrentCreated <= 0);

        // 消费数据
        std::cout << "Read Value: " << g_data[g_nStartIndex] \
                  << ", Index is " << g_nStartIndex << std::endl;

        g_nStartIndex++;
        if (g_nStartIndex == 20)
            g_nStartIndex = 0;

        ::Sleep(2000);

        // 释放资源
        g_nCurrentCreated--;
        g_semaphore.release();
    }
}

这里消费者,使用函数 g_semaphore.release(); 释放资源。
消费者每隔2000ms释放一次资源。

调用部分:

// 创建生产者和消费者
ConsumerThread consumer;
ProducerThread producer;
consumer.start();
producer.start();
// 等待线程结束
consumer.wait();
producer.wait();

运行结果:
Created Data: 0, Index is 0
Read Value: 0, Index is 0
Created Data: 1, Index is 1
Created Data: 2, Index is 2
Created Data: 3, Index is 3
Created Data: 4, Index is 4
Created Data: 5, Index is 5
Created Data: 6, Index is 6
Created Data: 7, Index is 7
Created Data: 8, Index is 8
Created Data: 9, Index is 9
Created Data: 10, Index is 10
Created Data: 11, Index is 11
Created Data: 12, Index is 12
Created Data: 13, Index is 13
Created Data: 14, Index is 14
Created Data: 15, Index is 15
Created Data: 16, Index is 16
Created Data: 17, Index is 17
Created Data: 18, Index is 18
Created Data: 19, Index is 19
Read Value: 1, Index is 1
Created Data: 20, Index is 0
Read Value: 2, Index is 2
Created Data: 21, Index is 1
Read Value: 3, Index is 3
Created Data: 22, Index is 2
Read Value: 4, Index is 4
Created Data: 23, Index is 3

首先生产者创建了一个数据,消费数据。因为消费者消费数据比较慢,因此创建者很快就将缓存创建满了。
当消费者再次消费数据后,生产者马上创建数据。

不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:线程的互斥和同步(7)- Qt的条件变量QWaitCondition