使用QWaitCondition實現一個簡單的線程池

上篇文章主要講了線程池的使用
Qt中的線程池QThreadPool
本篇文章使用Qt的條件變量 QWaitCondition ,實現一個簡單的線程池
關於 QWaitCondition 的使用,可以參照 線程的互斥和同步(7)- Qt的條件變量QWaitCondition

先來說一下簡單的思路,線程池初始化時創建一定數量的線程(8個) ,所有的線程處於掛起狀態。
當任務添加到列表時,喚醒一個線程執行任務。如果當前未執行任務數>線程池中最大線程數時,等待有空閒的線程再執行。
這裏定義了四個類來實現線程池:

  • CThreadPoolData : 數據類,存儲任務隊列,條件變量和互斥鎖以及線程循環條件的存儲
  • CThread :線程類,繼承自 QThread ,線程池中的每個線程對象,重寫了 run() 函數。
  • CRunnable : 任務類,同 QRunnable 。需要子類化 run() 函數。
  • CThreadPool :線程池類,管理多個線程對象。可以添加任務,喚醒線程執行。

完整代碼下載:
https://github.com/douzhongqiang/threadCode/tree/master/CThreadPool


接下來看一下代碼:
線程池類 CThreadPool 中的定義,頭文件:

class CThreadPool
{
public:
    CThreadPool();
    ~CThreadPool();

    // 添加任務
    void addTask(CRunnable* runnable);

private:
    // 線程池
    QVector<std::shared_ptr<CThread>> m_threads;
    // 線程數目
    int m_nThreadCount = 8;

    // 數據
    CThreadPoolData m_poolData;
};

函數 addTask 爲添加任務接口,m_threads 爲線程數組,存儲所有的線程對象。
m_poolData 表示需要用到的數據,它的定義如下:

// 線程池相關數據類
class CThreadPoolData
{
public:
    CThreadPoolData();
    ~CThreadPoolData();

    // 任務列表
    QList<std::shared_ptr<CRunnable>> m_taskList;
    // 互斥鎖和條件變量
    QMutex m_mutex;
    QWaitCondition m_waitCondition;
    // 結束線程控制
    std::atomic<bool> m_isRunning;
};

線程池的實現:

CThreadPool::CThreadPool()
{
    // 設置線程池數目
    m_nThreadCount = QThread::idealThreadCount();

    // 創建線程池數組,並初始化
    for (int i=0; i<m_nThreadCount; ++i)
    {
        CThread *pThread = new CThread(m_poolData);
        m_threads.push_back(std::shared_ptr<CThread>(pThread));

        pThread->start();
    }
}

CThreadPool::~CThreadPool()
{
    // 等待線程全部退出
    m_poolData.m_isRunning = false;

    // 全部喚醒
    m_poolData.m_waitCondition.wakeAll();

    // 等待所有線程退出
    for (auto iter = m_threads.begin(); iter != m_threads.end(); ++iter)
        (*iter)->wait();
}

void CThreadPool::addTask(CRunnable* runnable)
{
    QMutexLocker locker(&m_poolData.m_mutex);
    // 添加到任務列表
    m_poolData.m_taskList.push_back(std::shared_ptr<CRunnable>(runnable));
    // 喚醒一個線程,如果當前沒有休眠線程則該信號會被忽略
    m_poolData.m_waitCondition.wakeOne();
}

構造函數中,創建了 QThread::idealThreadCount() 個線程,放入到了線程對象數組中,並開啓所有線程。
析構函數中,喚醒所有的線程,並等待線程結束。變量 m_isRunning 是每個線程中,退出 while() 循環的控制變量。
addTask 函數,將任務存入到任務列表中,並喚醒一個線程執行。如果當前沒有掛起的線程,該信號會被忽略。

CThread 是繼承自 QThread 的線程類,它的頭文件定義:

class CThread : public QThread
{
    Q_OBJECT

public:
    CThread(CThreadPoolData& data);
    ~CThread();

    void run(void) override;

private:
    CThreadPoolData& m_data;
};

cpp文件:

CThread::CThread(CThreadPoolData& data)
    :m_data(data)
{

}

CThread::~CThread()
{

}

void CThread::run(void)
{
    while (m_data.m_isRunning)
    {
        // 加鎖並等待被喚醒
        QMutexLocker locker(&m_data.m_mutex);
        m_data.m_waitCondition.wait(&m_data.m_mutex);

        // 判斷隊列是否爲空
        while (!m_data.m_taskList.isEmpty())
        {
            auto runnable = m_data.m_taskList.takeFirst();
            locker.unlock();

            // Do Task(無鎖狀態執行)
            runnable->run();

            locker.relock();
        }
    }
}

run函數中,等待被喚醒,如果被喚醒,從任務隊列中取出任務,並執行。

CRunnable 爲任務基類,它的定義比較簡單

class CRunnable
{
public:
    CRunnable();
    virtual ~CRunnable();

    virtual void run(void) = 0;
};

接下來使用一個例子,測試一下這個線程池:
測試任務 TestTaskRunnable

頭文件:

#include "CRunnable.h"

class TestTaskRunnable : public CRunnable
{
public:
    void run(void) override;
};

cpp文件

std::atomic<int> g_number(0);

void TestTaskRunnable::run(void)
{
    std::cout << "Number is " << g_number++ \
              << ", Thread Id is " << QThread::currentThreadId() \
              << std::endl;
}

這裏任務執行的內容比較簡單,將全部變量 g_number 自增1,並打印線程ID。

main 函數中調用

int main(int argc, char *argv[])
{
    CThreadPool threadpool;
    for (int i = 0; i < 20; ++i)
    {
        TestTaskRunnable* taskRunnable = new TestTaskRunnable;
        threadpool.addTask(taskRunnable);
    }

    system("pause");
    return 0;
}

因爲這裏添加任務時使用的帶引用計數的智能指針,無需我們考慮內存釋放的問題。

運行結果:
Number is 0, Thread Id is 0000000000004DF8
Number is 8, Thread Id is 0000000000004DF8
Number is 9, Thread Id is 0000000000004DF8
Number is 7, Thread Id is 0000000000000468
Number is 4, Thread Id is 00000000000034D4
Number is 2, Thread Id is 0000000000001680
Number is 10, Thread Id is 0000000000004DF8
Number is 13, Thread Id is 0000000000004DF8
Number is 14, Thread Id is 0000000000004DF8
Number is 12, Thread Id is 0000000000001680
Number is 15, Thread Id is 0000000000001680
Number is 11, Thread Id is 00000000000034D4
Number is 1, Thread Id is 0000000000003D80
Number is 5, Thread Id is 0000000000003168
Number is 3, Thread Id is 000000000000057C
Number is 16, Thread Id is 00000000000034D4
Number is 6, Thread Id is 0000000000004220
Number is 19, Thread Id is 0000000000001680
Number is 18, Thread Id is 0000000000003168
Number is 17, Thread Id is 0000000000004DF8


作者: douzhq
個人博客主頁: 不會飛的紙飛機
微信公衆號: 不會飛的紙飛機 ,不定時更新技術文章和搞笑段子。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章