QT多執行緒程式設計
目錄
- 72.程序與執行緒的概念
- 73.Qt中的多執行緒程式設計
- 74.多執行緒間的同步
- 75.多執行緒間的互斥(上)
- 76.多執行緒間的互斥(下)
- 77.銀行家演算法的分析與實現
- 78.多執行緒中的訊號與槽(上)
- 79.多執行緒中的訊號與槽(中)
- 80.多執行緒中的訊號與槽(下)
- 81.訊號與槽的連線方式
- 82.執行緒的生命期問題
- 83.另一種建立執行緒的方式
- 84.多執行緒與介面元件的通訊(上)
- 85.多執行緒與介面元件的通訊(下)
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;
}