1. 程式人生 > 其它 >Qt-多執行緒及簡單例項

Qt-多執行緒及簡單例項

1. 概述

    通常情況下,應用程式都是在一個執行緒中執行操作。但是,當呼叫一個耗時操作(例如,大批量I/O或大量矩陣變換等CPU密集操作)時,使用者介面常常會凍結,而使用多執行緒可以解決這一問題

2. 優勢

(1) 提高應用程式的響應速度。這對於開發圖形介面尤為重要,當一個操作耗時很長時,整個系統都會等待這個操作,程式就不能響應鍵盤、滑鼠、選單等操作,二使用多執行緒可將耗時長的操作置於一個新的執行緒,從而避免出現以上問題

(2) 使多CPU系統更加有效。當執行緒數不大於CPU數目時,作業系統可以呼叫不同的執行緒運行於不同的CPU上

(3) 改善程式結構。一個既長又複雜的程序可以考慮分為多個執行緒,成為獨立或半獨立的執行部分,這樣有利於程式碼的理解和維護

3. 特點

(1) 多執行緒程式的行為無法預期,當多次執行上述程式時,每次的執行結果都可能不同

(2) 多執行緒的執行順序無法保證,它與作業系統的排程策略和執行緒優先順序等因素有關

(3) 多執行緒的切換可能發生在任何時刻、任何地點

(4) 由於多執行緒對程式碼的敏感度高,因此對程式碼的細微修改都可能產生意想不到的結果

4. 簡單例項

    單擊"start"按鈕將啟動數個工作執行緒(工作執行緒數目由MAXSIZE巨集決定),各個執行緒迴圈列印數字0~9,直到單擊"stop"按鈕終止所有執行緒為止

(1) threaddlg.h

#ifndef THREADDLG_H
#define THREADDLG_H

#include <QDialog>
#include <QPushButton>
#include <workthread.h>

#define MAXSIZE 1

class ThreadDlg : public QDialog
{
    Q_OBJECT
public:
    //explicit關鍵字禁止建構函式隱式型別轉換(C++ 11新特性)
    explicit ThreadDlg(QWidget *parent = nullptr);

signals:

public slots:
    void slotStart();  //槽函式用於啟動執行緒
    void slotStop();  //槽函式用於終止執行緒

private:
    QPushButton* m_startButton;
    QPushButton* m_stopButton;
    QPushButton* m_quitButton;

    WorkThread* workThread[MAXSIZE];  //記錄了所啟動的全部執行緒
};

#endif // THREADDLG_H

(2) threaddlg.cpp

#include "threaddlg.h"
#include <QHBoxLayout>

ThreadDlg::ThreadDlg(QWidget *parent) : QDialog(parent)
{
    m_startButton = new QPushButton(tr("start"));
    m_stopButton = new QPushButton(tr("stop"));
    m_quitButton = new QPushButton(tr("quit"));

    QHBoxLayout* mainLayout = new QHBoxLayout(this);
    mainLayout->addWidget(m_startButton);
    mainLayout->addWidget(m_stopButton);
    mainLayout->addWidget(m_quitButton);

    connect(m_startButton, SIGNAL(clicked()), this, SLOT(slotStart()));
    connect(m_stopButton, SIGNAL(clicked()), this, SLOT(slotStop()));
    connect(m_quitButton, SIGNAL(clicked()), this, SLOT(close()));
}

void ThreadDlg::slotStart()
{
    //使用兩個for迴圈,目的是使新建的執行緒儘可能同時開始執行
    for(int i = 0; i < MAXSIZE; ++i)
    {
        workThread[i] = new WorkThread();
    }

    for(int i = 0; i < MAXSIZE; ++i)
    {
        //呼叫QThread基類的start(),此函式將啟動WorkThread類的run(),從而使執行緒開始真正執行
        workThread[i]->start();
    }

    m_startButton->setEnabled(false);
    m_stopButton->setEnabled(true);
}

void ThreadDlg::slotStop()
{
    for(int i = 0; i < MAXSIZE; ++i)
    {
        //呼叫QThread基類的terminate(),依次終止儲存在workThread[]陣列中的WorkThread類例項
        workThread[i]->terminate();
        //terminate()並不會立刻終止這個執行緒,該執行緒何時終止取決於作業系統的排程策略,因此,
        //QThread基類的wait()使執行緒阻塞等待直到退出或超時
        workThread[i]->wait();
    }

    m_startButton->setEnabled(true);
    m_stopButton->setEnabled(false);
}

(3) workthread.h

#ifndef WORKTHREAD_H
#define WORKTHREAD_H

#include <QThread>

class WorkThread : public QThread
{
    Q_OBJECT
public:
    WorkThread();
protected:
    void run();
};

#endif // WORKTHREAD_H

(4) workthread.cpp

#include "workthread.h"
#include <QDebug>

WorkThread::WorkThread()
{

}

void WorkThread::run()
{
    while(true)
    {
        for (int i = 0; i < 10; ++i)
        {
            //此處不使用printf()的原因:執行緒將因為呼叫printf()而持有一個控制I/O的鎖(lock),多個執行緒同時呼叫printf()在某些
            //情況下將造成控制檯輸出阻塞,而使用qDebug()作為控制檯輸出則不會出現上述問題
            qDebug() << i << i << i << i << i << i << i << i;
        }
    }
}

(5) 執行結果,左邊的圖是啟動5個執行緒的執行結果,右邊的圖是啟動單一執行緒的執行結果。可以看出,單一執行緒的輸出是順序列印的,而多執行緒的輸出結果是亂序列印的,這正是多執行緒的一大特點