1. 程式人生 > 實用技巧 >QT多執行緒程式設計

QT多執行緒程式設計

目錄

72.程序與執行緒的概念

程式是計算機儲存系統中的資料檔案

  • 原始碼程式(文字檔案,描述程式行為和功能)
  • 可執行程式(二進位制檔案,直接載入並執行)

程序的概念

  • 廣義:程式關於某個資料集合的一次執行活動
  • 狹義:程式被載入到記憶體中執行後得到程序

程式和程序的區別

  • 程式是硬碟中靜態的檔案,由儲存系統中的一段二進位制表示
  • 程序是記憶體中動態的執行實體,包含資料段,程式碼段,PC指標等

程式和程序的聯絡

  • 一個程式可能對應多個程序,一個程式多次執行,每次執行產生一個程序
  • 一個程序可能包含多個程式,一個程式以來多個其他動態庫

在當代作業系統中,資源分配的基本單位是程序;而CPU排程執行的基本單位是執行緒

執行緒的概念

  • 程序內的一個執行單元
  • 作業系統中一個可排程的實體
  • 程序中相對獨立的一個控制流序列
  • 執行時的現場資料和其他排程所需的資訊

可執行程式載入執行的過程

  • 系統分配資源(記憶體,IO等)
  • 將PC指向main函式入口地址
  • 從PC指標包含的地址處開始執行(第一個執行緒)

深入理解程序和執行緒

  • 程序中可以存在多個執行緒共享程序資源
  • 執行緒是被排程的執行單元,而程序不是排程單元
  • 執行緒不能脫離程序單獨存在,只能依賴於程序執行
  • 執行緒有生命期,有誕生和死亡
  • 任意執行緒都可以建立其他新的執行緒

小結

  • 程式是物理儲存空間中的資料檔案
  • 程序是程式執行後得到的執行實體
  • 執行緒是程序內部的具體執行單元
  • 一個程序內部可以有多個執行緒存在
  • 程序是作業系統資源分配的基本單位
  • 執行緒是作業系統排程執行的基本單位

73.Qt中的多執行緒程式設計

Qt中通過 QThread 直接支援多執行緒

/* QThread中的關鍵成員函式
 *   void run()		//執行緒體函式,用於定義執行緒功能
 *   void start()	//啟動函式,將執行緒入口地址設定為 run 函式
 *   void terminate()	//強制結束執行緒(不推薦)
 * 如何優雅的結束執行緒?
 *   run() 函式執行結束是優雅終止執行緒的唯一方式
 *   線上程類中增加標誌變數 m_toStop (volatile bool)
 *   通過 m_toStop 的值判斷是否需要從 run() 函式返回
 */
#include <QtCore/QCoreApplication>
#include <QThread>
#include <QDebug>
class Sample : public QThread	//建立執行緒類
{
protected:
    volatile bool m_toStop;		//標誌變數
    void run()	//執行緒入口函式
    {
        qDebug() << objectName() << " : begin";
        int* p = new int[10000];
        for(int i=0; !m_toStop && (i<10); i++)	//延時並判斷標誌變數
        {
            qDebug() << objectName() << " : " << i;
            p[i] = i * i * i;
            msleep(500);
        }
        delete[] p;
        qDebug() << objectName() << " : end";
   }
public:
    Sample()
    {
        m_toStop = false;
    }
    void stop()
    {
        m_toStop = true;
    }
};
int main(int argc, char *argv[])	//主執行緒入口函式
{
    QCoreApplication a(argc, argv);
    qDebug() << "main begin";

    Sample t;	//建立子執行緒
    t.setObjectName("t");
    t.start();	//啟動子執行緒
    for(int i=0; i<100000; i++)
    {
        for(int j=0; j<10000; j++)
        {

        }
    }
    t.stop();
    //t.terminate();	//強制結束執行緒(不推薦)

    qDebug() << "main end";
    
    return a.exec();
}

74.多執行緒間的同步

/* bool QThread::wait(unsigned long time = ULONG_MAX) */
	qDebug() << "begin";
	QThread t;
	t.start();
	t.wait();	//等待子執行緒執行結束
	qDebug() << "end";

75.多執行緒間的互斥(上)

QMutex 類是一把執行緒鎖,保證執行緒間的互斥

/* QMutex 中的關鍵成員函式
 * void lock()
 *    當鎖空閒時,獲取鎖並繼續執行
 *    當鎖被獲取,阻塞並等待鎖釋放
 * void unlock()
 *   釋放鎖(同一把鎖的獲取和釋放鎖必須在同一執行緒中成對出現)
 * 注意:如果 mutex 在呼叫 unlock() 時處於空閒狀態,那麼程式的行為是未定義的!
 */
//生產者和消費者
#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>

static QMutex g_mutex;
static QString g_store;
class Producer : public QThread
{
protected:
    void run()
    {
        int count = 0;
        while(true)
        {
            g_mutex.lock();
            //do something with critical resource
            g_store.append(QString::number((count++) % 10));
            qDebug() << objectName() << " : " + g_store;
            g_mutex.unlock();
            msleep(1);
        }
    }
};
class Customer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex.lock();
            if( g_store != "" )
            {
                g_store.remove(0, 1);
                qDebug() << objectName() << " : " + g_store;
            }
            g_mutex.unlock();
            msleep(1);
        }
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    Producer p;
    Customer c;
    p.setObjectName("Producer");
    c.setObjectName("Customer");    
    p.start();
    c.start();

    return a.exec();
}

76.多執行緒間的互斥(下)

執行緒的死鎖概念

  • 執行緒間相互等待臨界資源而造成彼此無法繼續執行

發生死鎖的條件

  • 系統中存在多個臨界資源且臨界資源不可搶佔
  • 執行緒需要多個臨界資源才能繼續執行

死鎖的避免

  • 對所有的臨界資源都分配一個唯一的序號(r1,r2,......,rn)
  • 對應的執行緒鎖也分配同樣的序號(m1,m2,......,mn)
  • 系統中的每個執行緒按照嚴格遞增的次序請求資源

訊號量

  • 訊號量是特殊的執行緒鎖
  • 訊號量允許N個執行緒同時訪問臨界資源
  • Qt中直接支援訊號量(QSemaphore)
/* QSemaphore 物件中維護了一個整形值
 * acquire()使得該值減一,release()使得該值加一
 * 當該值為 0 時, acquire() 函式將阻塞當前執行緒
 */
QSemaphore sem(1);
sem.acquire();
//do something with critical resource
sem.release();

/* 等價如下使用執行緒鎖 */

QMutex mutex;
mutex.lock();
//do something with critical resource
mutex.unlock();

多個生產者消費者,使用訊號量實現高併發

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <Qdebug>

const int SIZE = 5;
unsigned char g_buff[SIZE] = {0};
QSemaphore g_sem_free(SIZE);
QSemaphore g_sem_used(0);

class Producer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            int value = qrand() % 256;

            g_sem_free.acquire();

            for(int i=0; i<SIZE; i++)
            {
                if( !g_buff[i] )
                {
                    g_buff[i] = value;

                    qDebug() << objectName() << " generate: {" << i << ", " << value << "}";

                    break;
                }
            }

            g_sem_used.release();

            sleep(2);
        }
    }
};

class Customer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_sem_used.acquire();

            for(int i=0; i<SIZE; i++)
            {
                if( g_buff[i] )
                {
                    int value = g_buff[i];

                    g_buff[i] = 0;

                    qDebug() << objectName() << " consume: {" << i << ", " << value << "}";

                    break;
                }
            }

            g_sem_free.release();

            sleep(1);
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Producer p1;
    Producer p2;
    Producer p3;

    p1.setObjectName("p1");
    p2.setObjectName("p2");
    p3.setObjectName("p3");

    Customer c1;
    Customer c2;

    c1.setObjectName("c1");
    c2.setObjectName("c2");

    p1.start();
    p2.start();
    p3.start();

    c1.start();
    c2.start();
    
    return a.exec();
}

77.銀行家演算法的分析與實現

演算法策略

  • 將資金優先借予資金需求較少的客戶

解決的問題

  • 保證資源分配的安全性

應用場景

  • 作業系統核心中的程序管理
  • 資料庫核心中的頻繁事務管理

Qt中的演算法實現方案

  • 使用多執行緒機制模擬客戶和銀行
  • 銀行優先分配資源給最小需求的客戶
  • 當客戶的資源需求無法滿足的時候
    • 收回已分配的資源
    • 結束執行緒

程式見程式碼檔案

78.多執行緒中的訊號與槽(上)

QThread類擁有發射訊號和定義槽函式的能力

關鍵訊號:

void started()		//執行緒開始執行時發射該訊號
void finished()		//執行緒完成執行時發射該訊號
void terminated()	//執行緒被異常終止時發射該訊號

如果程式中有多個執行緒,槽函式是在哪個執行緒中執行的?

  • 程序中存在棧空間的概念(區別於棧資料結構)
  • 棧空間專用於函式呼叫(儲存函式引數,區域性變數等)
  • 執行緒擁有獨立的棧空間(可呼叫其他函式)
  • 結論:只要函式體中沒有訪問臨界資源的程式碼,同一個函式可以被多個執行緒同時呼叫,且不會產生任何副作用

作業系統通過整形標識管理程序和執行緒

  • 程序擁有全域性唯一的ID值(PID)
  • 執行緒有程序內唯一的ID值(TID)

QThread中的關鍵靜態成員函式

QThread* currentThread()
Qt::HANDLE currentThreadId()

main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();	//執行緒ID

    TestThread t;
    MyObject m;

    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished()));
    QObject::connect(&t, SIGNAL(terminated()), &m, SLOT(getTerminated()));

    t.start();
    
    return a.exec();
}

MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = 0);
    
signals:
    
protected slots:
    void getStarted();
    void getFinished();
    void getTerminated();
};

#endif // MYOBJECT_H

MyObject.cpp

#include "MyObject.h"
#include <QThread>
#include <QDebug>

MyObject::MyObject(QObject *parent) :
    QObject(parent)
{
}

void MyObject::getStarted()
{
    qDebug() << "void MyObject::getStarted() tid = " << QThread::currentThreadId();
}

void MyObject::getFinished()
{
    qDebug() << "void MyObject::getFinished() tid = " << QThread::currentThreadId();
}

void MyObject::getTerminated()
{
    qDebug() << "void MyObject::getTerminated() tid = " << QThread::currentThreadId();
}

TestThread.h

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit TestThread(QObject *parent = 0);
    
signals:
    void testSignal();
protected slots:
    void testSlot();
};

#endif // TESTTHREAD_H

TestThread.cpp:建立一個執行緒,傳送自定義訊號

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

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}

void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();

    for(int i=0; i<10; i++)
    {
        qDebug() << "void TestThread::run() i = " << i;

        sleep(1);
    }

    emit testSignal();	//傳送訊號(用關鍵字 emit 後面加上要發的訊號)

    qDebug() << "void TestThread::run() -- end";
}

void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot() tid = " << currentThreadId();
}

79.多執行緒中的訊號與槽(中)

令人不解的問題:當槽函式是執行緒類中的成員時,為什麼依然不在本執行緒內被呼叫執行?

隱藏的問題

  • 物件依附於哪一個執行緒?
  • 物件的依附性與槽函式執行的關係?
  • 物件的依附性是否可以改變?

物件依附於哪一個執行緒?

  • 預設情況下,物件依附於自身被建立的執行緒;例如:物件在主執行緒( main()函式 )中被建立,則依附於主執行緒

物件的依附性與槽函式執行的關係?

  • 預設情況下,槽函式在其所依附的執行緒中被呼叫執行!

物件的依附性是否可以改變?

  • QObject::moveToThread 用於改變物件的執行緒依附性,使得物件的槽函式在依附的執行緒中被呼叫執行
int main(int argc, char *argv[])
{
	TestThread t;
    MyObject m;
    /* 改變物件 m 的執行緒依附性,使其依附於執行緒 t */
    m.moveToThread(&t);
}

改變依附性後,物件m的槽函式為什麼沒有被呼叫執行?

執行緒中的事件迴圈

  • 訊號與槽的機制需要事件迴圈的支援
  • QThread 類中提供的 exec() 函式用於開啟執行緒的事件迴圈
  • 只有事件迴圈開啟,槽函式才能在訊號傳送後被呼叫

研究槽函式的具體執行執行緒有什麼意義?

  • 當訊號的傳送與對應槽函式的執行在不同執行緒中時,可能產生臨界資源的競爭問題!

更改TestThread.cpp

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

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}

void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();

    for(int i=0; i<10; i++)
    {
        qDebug() << "void TestThread::run() i = " << i;

        sleep(1);
    }

    emit testSignal();	//傳送訊號(用關鍵字 emit 後面加上要發的訊號)
    
    exec();	//開啟事件迴圈

    qDebug() << "void TestThread::run() -- end";
}

void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot() tid = " << currentThreadId();
}

80.多執行緒中的訊號與槽(下)

如果執行緒體函式中開啟了事件迴圈,執行緒如何正常結束?

QThread::exec() 使得執行緒進入事件迴圈

  • 事件迴圈結束前,exec() 後的語句無法執行
  • quit() 和 exit() 函式用於結束事件迴圈
  • quit() 等價於 exit(0), exec() 的返回值由 exit() 引數決定

無論事件迴圈是否開啟,訊號傳送後會直接進入物件所依附執行緒的事件佇列;然而,只有開起了事件迴圈,對應的槽函式才會在執行緒中被呼叫

更改main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));

    m.moveToThread(&t);
    t.moveToThread(&t);

    t.start();

    t.wait(8 * 1000);	//等待8s

    t.quit();		//結束執行緒的事件迴圈

    return a.exec();
}

什麼時候需要線上程中開啟事件迴圈?

  • 事務性操作( 間斷性IO操作、檔案操作等 )可以開啟執行緒的事件迴圈;每次操作通過傳送訊號的方式使得槽函式在子執行緒中執行。

檔案緩衝區

  • 預設情況下,檔案操作時會開闢一段記憶體作為緩衝區
  • 向檔案中寫入的資料會先進入緩衝區
  • 只有當緩衝區滿或者遇見換行符才將資料寫入磁碟
  • 緩衝區的意義在於,減少磁碟的低階IO操作,提高檔案讀寫效率!

Qt執行緒的使用模式

  • 無事件迴圈模式
    • 後臺執行長時間的耗時任務:檔案複製,網路資料讀取等
  • 開啟事件迴圈模式
    • 執行事務性操作:檔案寫入,資料庫寫入等

main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "FileWriter.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    FileWriter writer("C:/Users/hp/Desktop/test.txt");

    if( writer.open() )
    {
        writer.write("D.T.Software\r\n");
        writer.write("ÖÐÎIJâÊÔ\r\n");
        writer.write("µÒÌ©Èí¼þ\r\n");
        writer.close();
    }

    return a.exec();
}

FileWriter.h

#ifndef FILEWRITER_H
#define FILEWRITER_H

#include <QObject>
#include <QFile>
#include <QThread>

class FileWriter : public QObject
{
    Q_OBJECT

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

    QFile m_file;
    Worker m_worker;
public:
    explicit FileWriter(QString file, QObject *parent = 0);
    bool open();
    void write(QString text);
    void close();
    ~FileWriter();
signals:
    void doWrite(QString text);
    void doClose();
protected slots:
    void writeSlot(QString text);
    void closeSlot();
};

#endif // FILEWRITER_H

FileWriter.cpp

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

void FileWriter::Worker::run()
{
    qDebug() << "void FileWriter::Worker::run() - begin: tid = " << currentThreadId();

    exec();

    qDebug() << "void FileWriter::Worker::run() - end";
}

FileWriter::FileWriter(QString file, QObject *parent) :
    QObject(parent), m_file(file)
{
    connect(this, SIGNAL(doWrite(QString)), this, SLOT(writeSlot(QString)));
    connect(this, SIGNAL(doClose()), this, SLOT(closeSlot()));

    moveToThread(&m_worker);

    m_worker.start();
}

bool FileWriter::open()
{
    return m_file.open(QIODevice::WriteOnly | QIODevice::Text);
}

void FileWriter::write(QString text)
{
    qDebug() << "void FileWriter::write(QString text) tid = " << QThread::currentThreadId();

    emit doWrite(text);
}

void FileWriter::close()
{
    qDebug() << "void FileWriter::close() tid = " << QThread::currentThreadId();

    emit doClose();
}

void FileWriter::writeSlot(QString text)
{
    qDebug() << "void FileWriter::writeSlot(QString text) tid = " << QThread::currentThreadId();

    m_file.write(text.toAscii());
    m_file.flush();
}

void FileWriter::closeSlot()
{
    qDebug() << "void FileWriter::closeSlot() tid = " << QThread::currentThreadId();

    m_file.close();
}

FileWriter::~FileWriter()
{
    m_worker.quit();
}

81.訊號與槽的連線方式

深入訊號與槽的連線方式

Qt::DirectConnection	//立即呼叫
Qt::QueuedConnection	//非同步呼叫
Qt::BlockingQueuedConnection	//同步呼叫
Qt::AutoConnection		//預設連線
Qt::UniqueConnection	//單一連線
    
bool connect(const QObject* sender,
             const char* signal,
             const QObject* receiver,
             const char* method,
             Qt::ConnectionType type = Qt::AutoConnection)

知識回顧:

  • 每一個執行緒都有自己的事件佇列

  • 執行緒通過事件佇列接收訊號

  • 訊號在事件迴圈中被處理

  • 訊號進入接收者物件所依附執行緒的事件佇列

Qt::DirectConnection //立即呼叫

  • 直接在傳送訊號的執行緒中呼叫槽函式,等價於槽函式的實時呼叫!

Qt::QueuedConnection //非同步呼叫

  • 訊號傳送至目標執行緒的事件佇列,由目標執行緒處理;當前執行緒繼續向下執行!

Qt::BlockingQueuedConnection //同步呼叫

  • 訊號傳送至目標執行緒的事件佇列,由目標執行緒處理;當前執行緒等待槽函式返回,之後繼續向下執行!注意:目標執行緒和當前執行緒必須不同

Qt::AutoConnection //預設連線

  • 當傳送執行緒 = 接收執行緒時,採用DirectConnection
  • 當傳送執行緒 != 接收執行緒時,採用QueuedConnection

Qt::UniqueConnection //單一連線

  • 功能與 AutoConnection 相同,自動確定連線型別
  • 同一個訊號與同一個槽函式之間只有一個連線

預設情況下,同一個訊號可以多次連線到同一個槽函式

多次連線意味著同一個槽函式的多次呼叫

82.執行緒的生命期問題

QThread物件的生命週期與對應的執行緒生命週期是否一致?

  • 工程實踐中的經驗準則:執行緒物件生命週期 > 對應的執行緒生命週期
//main中的區域性函式建立執行緒,區域性函式結束後執行緒物件就被銷燬了,此處是違規的
void test()
{
	MyThread t;
	t.start();
}
//執行緒的定義
class MyThread : public QThread 
{
protected:
	int i;
	void run() {
		this->i = 1;
		for(int i=0; i<5; i++) {
			this->i *= 2;
			sleep(1);
		}
	}
}

同步型執行緒設計

  • 概念
    • 執行緒物件主動等待執行緒生命期結束後才銷燬
  • 特點
    • 同時支援在棧和堆中建立執行緒物件
    • 物件銷燬時確保執行緒生命期結束
  • 要點
    • 在解構函式中先呼叫 wait() 函式,強制等到執行緒執行結束
  • 使用場合
    • 執行緒生命期相對較短的情形
void sync_thread()
{
    SyncThread st;
    st.start();
}

SyncThread::~SyncThread()
{
    wait();
	//do something to release resource
}

非同步型執行緒設計

  • 概念
    • 執行緒生命期結束時通知銷燬執行緒物件
  • 特點
    • 只能在堆中建立執行緒物件
    • 執行緒物件不能被外界主動銷燬
  • 要點
    • 在run()中最後呼叫deleteLater()函式
    • 執行緒體函式主動申請銷燬執行緒物件
  • 使用場合
    • 執行緒生命期不可控,需要長時間運行於後臺的情形
void async_thread()
{
    AsyncThread* at = AsyncThread::NewInstance();
    at->start();
}
void AsyncThread::run()
{
    qDebug() << "void AsyncThread::run() tid = " << currentThreadId();

    for(int i=0; i<3; i++)
    {
        //do something complicated
    }
    //apply to destory thread object
    deleteLater();
}

83.另一種建立執行緒的方式

面向物件程式設計實踐的早期,工程中習慣於通過繼承的方式擴充套件系統的功能

class QThread : public Qt
{
protected:
	virtual void run() = 0;
}

現代軟體架構技術

  • 儘量使用組合的方式實現系統功能
  • 程式碼中僅體現需求中的繼承關係

通過繼承的方式實現新的執行緒類有什麼實際意義?

  • 通過繼承的方式實現多執行緒沒有任何實際意義
  • QThread對應於作業系統中的執行緒
  • QThread用於充當一個執行緒操作的集合
  • 應該提供靈活的方式制定執行緒入口函式
  • 儘量避免重寫 void run()

QThread 類的改進

class QThread : public QObject
{
	Q_OBJECT
protected:
	virtual void run()
	{
		(void)exec();
	}
}

如何靈活的指定一個執行緒物件的執行緒入口函式?

解決方案-訊號與槽

  • 在類中定義一個槽函式 void tmain() 作為執行緒入口函式
  • 在類中定義一個 QThread 成員物件 m_thread
  • 改變當前物件的執行緒依附性到 m_thread
  • 連線 m_thread 的 start() 訊號到 tmain()

main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "AnotherThread.h"

void test()
{
    AnotherThread at;

    at.start();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    test();
    
    return a.exec();
}

AnotherThread.h

#ifndef ANOTHERTHREAD_H
#define ANOTHERTHREAD_H

#include <QObject>
#include <QThread>

class AnotherThread : public QObject
{
    Q_OBJECT

    QThread m_thread;
protected slots:
    void tmain();
public:
    explicit AnotherThread(QObject *parent = 0);
    void start();
    void terminate();
    void exit(int c);
    ~AnotherThread();
    
};

#endif // ANOTHERTHREAD_H

AnotherThread.cpp

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

AnotherThread::AnotherThread(QObject *parent) :
    QObject(parent)
{
    moveToThread(&m_thread);

    connect(&m_thread, SIGNAL(started()), this, SLOT(tmain()));
}

void AnotherThread::tmain()
{
    qDebug() << "void AnotherThread::tmain() tid = " << QThread::currentThreadId();

    for(int i=0; i<10; i++)
    {
        qDebug() << "void AnotherThread::tmain() i = " << i;
    }

    qDebug() << "void AnotherThread::tmain() end";

    m_thread.quit();	//主動結束執行緒的事件迴圈
}

void AnotherThread::start()
{
    m_thread.start();
}

void AnotherThread::terminate()
{
    m_thread.terminate();
}

void AnotherThread::exit(int c)
{
    m_thread.exit(c);
}

AnotherThread::~AnotherThread()
{
    m_thread.wait();
}

84.多執行緒與介面元件的通訊(上)

是否可以在子執行緒中建立介面元件?

GUI系統設計原則

  • 所有介面元件的操作都只能在主執行緒中完成;因此,主執行緒也叫做UI執行緒!

子執行緒如何對介面元件進行更新?

解決方案-訊號與槽

  • 在子執行緒類中定義介面元件的更新訊號( UpdateUI )
  • 在主視窗類中定義更新介面元件的槽函式( SetInfo)
  • 使用非同步方式連線更新訊號到槽函式( UpdateUI -> SetInfo)
    • 子執行緒通過發射訊號的方式更新介面元件
    • 所有的介面元件物件只能依附於主執行緒

main.cpp

#include <QtGui/QApplication>
#include "Widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    
    return a.exec();
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;
protected slots:
    void appendText(QString text);
public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "TestThread.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    // TestThread* ptt = new TestThread();

    // ptt->start();

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    connect(&m_thread, SIGNAL(updateUI(QString)), this, SLOT(appendText(QString)));

    m_thread.start();
}

void Widget::appendText(QString text)
{
    textEdit.appendPlainText(text);
}

Widget::~Widget()
{
    
}

UpdateThread.h

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit UpdateThread(QObject *parent = 0);
    
signals:
    void updateUI(QString text);
    
};

#endif // UPDATETHREAD_H

UpdateThread.cpp

#include "UpdateThread.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    emit updateUI("Begin");

    for(int i=0; i<10; i++)
    {
        emit updateUI(QString::number(i));

        sleep(1);
    }

    emit updateUI("End");
}

85.多執行緒與介面元件的通訊(下)

子執行緒能夠更改介面元件狀態的本質是什麼?

  • 子執行緒發射訊號通知主執行緒介面更新請求;主執行緒根據具體訊號以及訊號引數對介面元件進行修改。

是否有其他間接的方式可以讓子執行緒更新介面元件的狀態?

解決方案-傳送自定義事件

  • 自定義事件類用於描述介面更新細節
  • 在主視窗中重寫事件處理函式 event
  • 使用 postEvent 函式(非同步方式)傳送自定義事件類物件
    • 子執行緒指定接收訊息的物件為主視窗物件
    • 在 event 事件處理函式更新介面狀態

main.cpp

#include <QtGui/QApplication>
#include "Widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    
    return a.exec();
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;

public:
    Widget(QWidget *parent = 0);
    bool event(QEvent *evt);
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "StringEvent.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    m_thread.setParent(this);
    m_thread.start();
}

bool Widget::event(QEvent *evt)
{
    bool ret = true;

    if( evt->type() == StringEvent::TYPE )
    {
        StringEvent* se = dynamic_cast<StringEvent*>(evt);

        if( se != NULL )
        {
            textEdit.appendPlainText(se->data());
        }
    }
    else
    {
        ret = QWidget::event(evt);
    }

    return ret;
}

Widget::~Widget()
{
    
}

UpdateThread.h

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit UpdateThread(QObject *parent = 0);
    
};

#endif // UPDATETHREAD_H

UpdateThread.cpp

#include "UpdateThread.h"
#include <QApplication>
#include "StringEvent.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    // emit updateUI("Begin");
    QApplication::postEvent(parent(), new StringEvent("Begin"));

    for(int i=0; i<10; i++)
    {
        // emit updateUI(QString::number(i));
        QApplication::postEvent(parent(), new StringEvent(QString::number(i)));

        sleep(1);
    }

    // emit updateUI("End");
    QApplication::postEvent(parent(), new StringEvent("End"));
}

StringEvent.h

#ifndef _STRINGEVENT_H_
#define _STRINGEVENT_H_

#include <QEvent>
#include <QString>

class StringEvent : public QEvent
{
    QString m_data;
public:
    const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);

    explicit StringEvent(QString data = "");
    QString data();
    
};

#endif // _STRINGEVENT_H_

StringEvent.cpp

#include "StringEvent.h"

StringEvent::StringEvent(QString data) : QEvent(TYPE)
{
    m_data = data;
}

QString StringEvent::data()
{
    return m_data;
}