Qt多線程編程爬坑筆記

最近在工作中用到了Qt中的多線程,踩了不少坑,故作下筆記角寸,警示后人 - -!


彩虹籠罩下的華農(nóng)

Overview

使用多線程編程可以最大限度地調用CPU資源忿墅,尤其對于多處理器系統(tǒng)扁藕。而對于界面開發(fā)而言,多線程一個十分重要的作用就是將復雜的運算處理分開執(zhí)行疚脐,以免造成界面的卡頓和凍結亿柑,甚至被系統(tǒng)強制關閉。

先來看看官方文檔是怎么教我們搞Qt的多線程的棍弄。首先望薄,可以建立一個Woker類和一個Controller類,想要放在線程中的后臺處理放在Worker類中呼畸,再使用QObject::moveToThread()與子線程關聯(lián)痕支,如下。

class Worker : public QObject {
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

class Controller : public QObject {
    Q_OBJECT
        QThread workerThread; //建立QThread對象
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread); //令worker和workerThread關聯(lián)
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

可以看到役耕,通過信號槽機制采转,可以控制放在了子線程中的worker并互相通信。由于線程中的信號槽連接默認是隊列連接方式(稍后會講到)瞬痘,你可以安全地連接Worker對象與任意的Qobject對象。

Another way to make code run in a separate thread, is to subclass QThread and reimplement run()板熊。另一種方法就是繼承QThread并重寫run函數(shù)框全,如下

class WorkerThread : public QThread {
    Q_OBJECT
        void run() override {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }
signals:
    void resultReady(const QString &s);
};

void MyObject::startWorkInAThread() {
    WorkerThread *workerThread = new WorkerThread(this);
    connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
}

詳細的內容自己去看吧,具體用哪種方法看個人習慣了干签。好像乍眼一看也沒什么難的~然而

一號坑:子線程中操作UI

Qt創(chuàng)建的子線程中是不能對UI對象進行任何操作的津辩,即QWidget及其派生類對象,這個是我掉的第一個坑〈兀可能是由于考慮到安全性的問題闸度,所以Qt中子線程不能執(zhí)行任何關于界面的處理,包括消息框的彈出蚜印。正確的操作應該是通過信號槽莺禁,將一些參數(shù)傳遞給主線程,讓主線程(也就是Controller)去處理窄赋。

void Controller::handleResults(const int rslt, QString code,
    QString id, QString desc, QString time) 
{
    lockThread->lck->lock_log << "controller handleResults start!\n";
    lockThread->lck->lock_log.flush();
    //(打開時)加密鎖有效
    if (rslt == 1) {
        lockThread->lck->lock_log << "controller handleResults 1!\n";
        lockThread->lck->lock_log.flush();
        m_isAuthorized = true;
        QMessageBox::information(NULL, QStringLiteral("授權信息"),
            QStringLiteral("授權成功!!"), QMessageBox::Yes);
    }
    //加密鎖失效
    else if (rslt == 2) {
        lockThread->lck->lock_log << "controller handleResults 2!\n";
        lockThread->lck->lock_log.flush();
        m_isAuthorized = false;
        QMessageBox::information(NULL, QStringLiteral("授權信息"),
            QStringLiteral("鎖ID:") + id + "\n" +
            QStringLiteral("校驗碼:") + code, QMessageBox::Yes);
    }
....

個人在VS2017環(huán)境下哟冬,在子線程中操作UI是直接崩潰的。

二號坑:信號的參數(shù)問題

這個就實屬有毒忆绰,搞了我好久浩峡。這個涉及到了Qt的元對象系統(tǒng)(Meta-Object System)和信號槽機制。

元對象系統(tǒng)即是提供了Qt類對象之間的信號槽機制的系統(tǒng)错敢。要使用信號槽機制翰灾,類必須繼承自QObject類,并在私有聲明區(qū)域聲明Q_OBJECT宏稚茅。當一個cpp文件中的類聲明帶有這個宏预侯,就會有一個叫moc工具的玩意創(chuàng)建另一個以moc開頭的cpp源文件(在debug目錄下),其中包含了為每一個類生成的元對象代碼峰锁。


moc開頭的cpp文件

在使用connect函數(shù)的時候萎馅,我們一般會把最后一個參數(shù)忽略掉。這時候我們需要看下函數(shù)原型

[static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, 
    const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

可以看到虹蒋,最后一個參數(shù)代表的是連接的方式糜芳。


Qt::ConnectionType

我們一般會用到方式是有三種

  • 自動連接(AutoConnection),默認的連接方式魄衅。如果信號與槽峭竣,也就是發(fā)送者與接受者在同一線程,等同于直接連接晃虫;如果發(fā)送者與接受者處在不同線程皆撩,等同于隊列連接。

  • 直接連接(DirectConnection)哲银。當信號發(fā)射時扛吞,槽函數(shù)立即直接調用。無論槽函數(shù)所屬對象在哪個線程荆责,槽函數(shù)總在發(fā)送者所在線程執(zhí)行滥比。

  • 隊列連接(QueuedConnection)。當控制權回到接受者所在線程的事件循環(huán)時做院,槽函數(shù)被調用盲泛。這時候需要將信號的參數(shù)塞到信號隊列里濒持。槽函數(shù)在接受者所在線程執(zhí)行。

所以在線程間進行信號槽連接時寺滚,使用的是隊列連接方式柑营。在項目中,我定義的信號和槽的參數(shù)是這樣的

signals:
    //自定義發(fā)送的信號
    void myThreadSignal(const int, string, string, string, string);

貌似沒什么問題村视,然而實際運行起來槽函數(shù)根本就沒有被調用官套,程序沒有崩潰,VS也沒報錯蓖议。在查閱了N多博客和資料中才發(fā)現(xiàn)虏杰,在線程間進行信號槽連接時,參數(shù)不能隨便寫勒虾。

為什么呢纺阔?我的后四個參數(shù)是標準庫中的string類型,這不是元對象系統(tǒng)內置的類型修然,也不是c++的基本類型笛钝,系統(tǒng)無法識別,然后就沒有進入信號槽隊列中了愕宋,自然就會出現(xiàn)問題玻靡。解決方法有三種,最簡單的就是使用Qt的數(shù)據(jù)類型了

signals:
    //自定義發(fā)送的信號
    void myThreadSignal(const int, QString, QString, QString, QString);

第二種方法就是往元對象系統(tǒng)里注冊這個類型中贝。注意囤捻,在qRegisterMetaType函數(shù)被調用時,這個類型應該要確保已經(jīng)被完好地定義了邻寿。

qRegisterMetaType<MyClass>("MyClass");

方法三是改變信號槽的連接方式蝎土,將默認的隊列連接方式改為直接連接方式,這樣的話信號的參數(shù)直接進入槽函數(shù)中被使用绣否,槽函數(shù)立刻調用誊涯,不會進入信號槽隊列中。但這種方式官方認為有風險蒜撮,不建議使用暴构。

connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::DirectConnection)

至此,這個坑應該是能爬出來了段磨。

其它需要注意的問題

還有幾點需要注意

  • 一定要用信號槽機制取逾,別想著直接調用,你會發(fā)現(xiàn)并沒有在子線程中執(zhí)行薇溃。

  • 自定義的類不能指定父對象菌赖,因為moveToThread函數(shù)會將線程對象指定為自定義的類的父對象,當自定義的類對象已經(jīng)有了父對象沐序,就會報錯琉用。

  • 當一個變量需要在多個線程間進行訪問時,最好加上voliate關鍵字策幼,以免讀取到的是舊的值邑时。當然,Qt中提供了線程同步的支持特姐,比如互斥鎖之類的玩意晶丘,使用這些方式來訪問變量會更加安全。

C++11標準中直接可以使用標準庫中提供的多線程類唐含,但個人覺得還是Qt好用

  • Qt也提供了并發(fā)編程的支持浅浮,雖然這些玩意一般在服務器編程和高并發(fā)場景下用的比較多,界面編程比較少用捷枯。

至此總結完畢滚秩,希望您爬坑愉快 ( :

Reference:
Qt多線程間信號槽傳遞非QObject類型對象的參數(shù)
QT子線程與主線程的信號槽通信
Qt Creator快速入門_第三版 ——霍亞飛
Qt官方文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市淮捆,隨后出現(xiàn)的幾起案子郁油,更是在濱河造成了極大的恐慌,老刑警劉巖攀痊,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桐腌,死亡現(xiàn)場離奇詭異,居然都是意外死亡苟径,警方通過查閱死者的電腦和手機案站,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棘街,“玉大人蟆盐,你說我怎么就攤上這事〉疟蹋” “怎么了舱禽?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恩沽。 經(jīng)常有香客問我誊稚,道長,這世上最難降的妖魔是什么罗心? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任里伯,我火速辦了婚禮,結果婚禮上渤闷,老公的妹妹穿的比我還像新娘疾瓮。我一直安慰自己,他們只是感情好飒箭,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布狼电。 她就那樣靜靜地躺著蜒灰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肩碟。 梳的紋絲不亂的頭發(fā)上强窖,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機與錄音削祈,去河邊找鬼翅溺。 笑死,一個胖子當著我的面吹牛髓抑,可吹牛的內容都是我干的咙崎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吨拍,長吁一口氣:“原來是場噩夢啊……” “哼褪猛!你這毒婦竟也來了?” 一聲冷哼從身側響起密末,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤握爷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后严里,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體新啼,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年刹碾,在試婚紗的時候發(fā)現(xiàn)自己被綠了燥撞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡迷帜,死狀恐怖物舒,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情戏锹,我是刑警寧澤冠胯,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站锦针,受9級特大地震影響荠察,放射性物質發(fā)生泄漏。R本人自食惡果不足惜奈搜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一悉盆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧馋吗,春花似錦焕盟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灼卢。三九已至,卻和暖如春堰怨,著一層夾襖步出監(jiān)牢的瞬間芥玉,已是汗流浹背蛇摸。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工备图, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赶袄。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓揽涮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饿肺。 傳聞我的和親對象是個殘疾皇子蒋困,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354