天天看點

Qt實作簡單線程池

Qt本身就具備線程池類QThreadPool,使用起來也很友善,不過現在我們用QThread來做一個自己的線程池。

建立用來管理線程池的類ThreadPool,在這裡它發揮着管家的職責,掌管公共資源的使用。由于管理任務的隊列、鎖以及條件變量都聲明了私有,是以給WorkThread聲明為友元類,以便其能通路。

為了簡單,我們隻定義了一個構造函數以及三個函數:

  • ThreadPool(int count,QObject*parent=nullptr) 其中count表示要線上程池中建立多少個子線程
               
  • void PushTask(Task) 用于添加待執行的任務,Task是個函數對象,我們在調用的時候就不用顯示的指定參數了,其功能就是實作簡單的加法
  • void Start() 控制開始運作
  • void Destroy() 銷毀所有子線程

關于子線程的管理我們使用的智能指針,以友善資源自動釋放。下面就是ThreadPool的實作:

//頭檔案

//線程池
class ThreadPool : public QObject
{
    Q_OBJECT
public:
    explicit ThreadPool(int count,QObject *parent = nullptr);
    ~ThreadPool();

    void PushTask(Task);
    void Start();
    void Destroy();

private:
    QList<Task> m_Tasks; // 任務隊列
    QList<QSharedPointer<WorkThread>> m_Threads; //運作的線程
    bool m_bStop; //停止線程

    QMutex m_TaskMutex;
    QWaitCondition m_NotEmpty;

    friend class WorkThread;
};

//實作
ThreadPool::ThreadPool(int count,QObject *parent) : QObject(parent),m_bStop(false)
{
    for(int i=0;i<count;i++)
        m_Threads.push_back( QSharedPointer<WorkThread>(new WorkThread(this)));
}

ThreadPool::~ThreadPool()
{
    Destroy();
}

void ThreadPool::PushTask(Task task)
{
    m_TaskMutex.lock();
    m_Tasks.push_back(task);
    m_NotEmpty.wakeOne();
    m_TaskMutex.unlock();
}

void ThreadPool::Start()
{
    foreach (auto thread, m_Threads)
    {
        thread->start();
    }
}

void ThreadPool::Destroy()
{
    m_bStop = true;
    m_NotEmpty.wakeAll();

    //等待線程退出
    foreach (auto thread, m_Threads)
    {
        if(!thread->isFinished())
            thread->wait();
    }

    //清空 智能指針自動釋放
    m_Threads.clear();
}
           

以WorkThread做為線程池中的子線程,是以要繼承QThread,并重新實作run()函數。

由于公共資源都在ThreadPool中,是以我們定義了一個父類指針m_Pool來使用公共資源。

還定義了 m_Handle,主要為了下析構時列印線程ID,關于這塊大家可以參考我以前測試的一個例子https://blog.csdn.net/hanzhaoqiao1436/article/details/80957945

實作如下:

//頭檔案

//執行線程
class WorkThread:public QThread
{
public:
    WorkThread(ThreadPool* parent);
    ~WorkThread();

protected:
    virtual void run();

private:
    ThreadPool* m_Pool;
    Qt::HANDLE m_Handle;
};

//實作
WorkThread::WorkThread(ThreadPool *parent)
{
    m_Pool = parent;
}

WorkThread::~WorkThread()
{
    qDebug() << __FUNCTION__ << m_Handle << "Thread quit: " << isFinished();
}

void WorkThread::run()
{
    m_Handle = QThread::currentThreadId();
    while(!m_Pool->m_bStop)
    {
        m_Pool->m_TaskMutex.lock();
        while(m_Pool->m_Tasks.isEmpty())
        {
            qDebug() << QThread::currentThreadId() << QString::fromLocal8Bit("進入等待");
            m_Pool->m_NotEmpty.wait(&m_Pool->m_TaskMutex);
            qDebug() << QThread::currentThreadId() << QString::fromLocal8Bit("被喚醒");

            //線程退出條件判斷
            if(m_Pool->m_bStop)
            {
                m_Pool->m_TaskMutex.unlock();
                return;
            }
        }

        //取出任務
        Task task = m_Pool->m_Tasks.front();
        m_Pool->m_Tasks.pop_front();
        m_Pool->m_TaskMutex.unlock();

        if(task)
        {
            //執行任務
            task();
        }
    }
}
           

差點忘了貼函數對象Task的定義-_-|| I'm sorry

//任務類型
class Task
{
public:
    Task(int a,int b):m_a(a),m_b(b){}
    void operator ()()const
    {
        QThread::msleep(300);
        qDebug() << QThread::currentThreadId() << QString::fromLocal8Bit("執行任務") << m_a << "+" << m_b << "=" << m_a+m_b;
    }
    operator bool() const
    {
        //如果做除法這裡可以判斷下
        if(m_b == 0)
            return false;

        return true;
    }
private:
    int m_a,m_b;
};
           

調用測試:

//建立三個子線程
    m_Pool(new ThreadPool(3,this));

    //直接啟動線程
    m_Pool->Start();

    //此時能看到所有線程都進入等待狀态
    QThread::msleep(100);

    //添加任務
    for(int i=0;i<4;i++)
    {
        m_Pool->PushTask(Task(qrand(),qrand()));
    }
           

運作結果:

0x17fc "進入等待"
0x1990 "進入等待"
0x1298 "進入等待"
0x1298 "被喚醒"
0x1990 "被喚醒"
0x17fc "被喚醒"
0x1990 "執行任務" 26500 + 6334 = 32834
0x1298 "執行任務" 18467 + 41 = 18508
0x17fc "執行任務" 15724 + 19169 = 34893
0x1298 "進入等待"
0x17fc "進入等待"
0x1990 "執行任務" 29358 + 11478 = 40836
0x1990 "進入等待"

(調用Destroy)
0x1298 "被喚醒"
0x17fc "被喚醒"
0x1990 "被喚醒"
~WorkThread 0x1298 Thread quit:  true
~WorkThread 0x1990 Thread quit:  true
~WorkThread 0x17fc Thread quit:  true
           

假如我們在Destroy()中,不停止直接釋放子線程,線程會自動停下來嗎?答案當然是否定的。比如說我們注釋掉停止的相關調用

void ThreadPool::Destroy()
{
    //m_bStop = true;
    //m_NotEmpty.wakeAll();

    //等待線程退出
    //foreach (auto thread, m_Threads)
    //{
    //    if(!thread->isFinished())
    //        thread->wait();
    //}

    //清空 智能指針自動釋放
    m_Threads.clear();
}


(會看到如下結果)
~WorkThread 0x1958 Thread quit:  false
QThread: Destroyed while thread is still running
           

(附上程式源碼,歡迎大家指點錯誤)