日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Qt多线程中的信号与槽

發布時間:2025/4/5 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Qt多线程中的信号与槽 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 1 多線程中的信號與槽
    • 2 對象的依附性
      • 2.1 對象的依附性
      • 2.2 開啟線程事件循環
      • 2.3 線程事件循環的結束
      • 2.4 設計實例
    • 3 信號與槽的連接方式
      • 3.1 Qt::DirectConnection(立即調用)
      • 3.2 Qt::QueuedConnection(異步調用)
      • 3.3 Qt::BlockingQueuedConnection(同步調用)
      • 3.4 Qt::AutoConnection(默認連接)
      • 3.5 Qt::UniqueConnection(單一連接)

1 多線程中的信號與槽

值得思考的問題:

  • 線程對象是否可以發射信號(signal)?
  • 是否可以定義槽函數(slot)?

QThread類擁有發射信號和定義槽函數的能力,關鍵的信號如下:

  • void started():
    • 線程開始運行時發射該信號。
  • void finished():
    • 線程完成運行時發射該信號。
  • void terminated():
    • 線程被異常終止時發射該信號。

讓人逃避的問題:

  • 如果程序中有多個線程,槽函數是在哪個線程中執行的?

概念小科普:

  • 進程中存在??臻g的概念(區別于棧數據結構)。
  • ??臻g專用于函數調用(保存函數參數、局部變量等)。
  • 線程擁有獨立的??臻g(可調用其它函數)。

小結論:

  • 只要函數體中沒有訪問臨界資源的代碼,同一個函數可以被多個線程同時調用,且不會產生任何副作用!

實驗前的準備:

  • 操作系統通過整型標識管理進程和線程:
    • 進程擁有全局唯一的ID值(PID)。
    • 線程有進程內唯一的ID值(TID)。
  • QThread中的關鍵靜態成員函數:
    • QThread* currentThread()。
    • Qt::HANDLE currentThreadId()。

編程實驗:槽函數的運行上下文

MyThread.h:

#ifndef MYTHREAD_H #define MYTHREAD_H#include <QThread>class MyThread : public QThread {Q_OBJECT public:explicit MyThread(QObject *parent = 0);signals:void testSignal(); public slots:void onTestSignal();protected:void run(); };#endif // MYTHREAD_H

MyThread.cpp:

#include "MyThread.h" #include <QDebug>MyThread::MyThread(QObject *parent) :QThread(parent) {connect(this, SIGNAL(testSignal()), this, SLOT(onTestSignal())); }void MyThread::run() {for (int i=0; i<10; i++){qDebug() << objectName() << " : " << i << " " << QThread::currentThreadId();msleep(200);}emit testSignal(); }void MyThread::onTestSignal() {qDebug() << "onTestSignal() : " << QThread::currentThreadId(); }

MyObject.h:

#ifndef MYOBJECT_H #define MYOBJECT_H#include <QObject>class MyObject : public QObject {Q_OBJECT public:explicit MyObject(QObject *parent = 0);signals:public slots:void onStarted();void onFinished();void onTerminated();};#endif // MYOBJECT_H

MyObject.cpp:

#include "MyObject.h" #include <QThread> #include <QDebug>MyObject::MyObject(QObject *parent) :QObject(parent) { }void MyObject::onStarted() {qDebug() << "onStarted() : " << QThread::currentThreadId(); }void MyObject::onFinished() {qDebug() << "onFinished() : " << QThread::currentThreadId(); }void MyObject::onTerminated() {qDebug() << "onFinished() : " << QThread::currentThreadId(); }

main.cpp:

#include <QtCore/QCoreApplication> #include <QThread> #include <QDebug> #include "MyThread.h" #include "MyObject.h"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug() << QThread::currentThreadId();MyObject o;MyThread t;t.setObjectName("t");QObject::connect(&t, SIGNAL(started()), &o, SLOT(onStarted()));QObject::connect(&t, SIGNAL(finished()), &o, SLOT(onFinished()));QObject::connect(&t, SIGNAL(terminated()), &o, SLOT(onTerminated()));t.start();return a.exec(); }

運行結果如下:

令人不解的問題:

  • 當槽函數是線程中類的成員時,為什么依然不在本線程內被調用執行?

2 對象的依附性

2.1 對象的依附性

為了解決上述問題,我們需要弄清楚如下三個隱藏的問題:

  • 對象依附于哪一個線程?
  • 對象的依附性與槽函數執行的關系?
  • 對象的依附性是否可以改變?

對象依附于哪一個線程?

  • 默認情況下,對象依附于自身被創建的線程;例如:對象在主線程(main()函數)中被創建,則依附于主線程。

對象的依附性與槽函數執行的關系?

  • 默認情況下,槽函數在其所依賴的線程中被調用執行!

對象的依附性是否可以改變?

  • QObject::moveToThread用于改變對象的線程依附性,使得對象的槽函數在依附的線程中被調用執行。

    我們將代碼進行簡單修改,main函數如下:

    TestThread.cpp的內容如下:

    執行結果如下:

出現了一個很奇怪的問題:

  • 實驗中對象m的槽函數為什么沒有被調用執行?

2.2 開啟線程事件循環

對此,我們需要了解下線程中的事件循環:

  • 信號與槽的機制需要事件循環的支持。
  • QThread類中提供的exec()函數用于開啟線程的事件循環。
  • 只有事件循環開啟,槽函數才能在信號發送后被調用。


小結論:

  • 前提條件:
    • 對象依附的線程開啟了事件循環。
  • 后置結果:
    • 對象中的槽函數在依附的線程中被調用執行。

我們將TestThread.cpp的內容進行簡單的修改:

執行結果如下:

再來進一步實驗,如果我們將t的依附線程更改為自己會發生什么呢?
對main函數進行如下修改:

輸出結果如下:

我們可以看到槽函數在信號發送時立刻執行,而不是在開啟事件循環后才執行!

我們再來看一下研究槽函數的具體執行線程有什么意義?

  • 當信號的發送與對應槽函數的執行在不同線程中時,可能產生臨界資源的競爭問題!

2.3 線程事件循環的結束

有趣的問題:

  • 如果線程體函數中開啟了事件循環,線程如何正常結束呢?

QThread::exec()使得線程進入事件循環:

  • 事件循環結束前,exec()后的語句無法執行。
  • quit()和exit()函數用于結束事件循環。
  • quit()相當于exit(0),exec()的返回值由exit()參數決定。

注意:

  • 無論事件循環是否開啟,信號發送后會直接進入對象所依附線程的事件隊列;然而,只有開啟了事件循環,對應的槽函數才會在線程中被調用。

main函數修改為如下即可:

2.4 設計實例

設計相關的問題:

  • 什么時候需要在線程中開啟事件循環?

設計原則:

  • 事務性操作(間斷性IO操作等)可以開啟線程的事件循環;每次操作通過發送信號的方式使得槽函數在子線程中執行。

文件緩沖區:

  • 默認情況下,文件操作時會開辟一段內存作為緩沖區。
  • 向文件中寫入的數據會先進入緩沖區。
  • 只有當緩沖區滿或者遇見換行符才將數據寫入磁盤。
  • 緩沖區的意義在于,減少磁盤的低級IO操作,提高文件讀寫效率!

編程實驗:文件操作示例

FileWriter.h:

#ifndef FILEWRITER_H #define FILEWRITER_H#include <QObject> #include <QFile> #include <QThread>class FileWriter : public QObject {Q_OBJECTclass 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(); }

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("中文測試\r\n");writer.write("狄泰軟件\r\n");writer.close();}return a.exec(); }

Qt線程的使用模式:

  • 無事件循環模式:
    • 后臺執行長時間的耗時任務:文件復制、網絡數據讀取等。
  • 開啟事件循環模式:
    • 執行事務性操作:文件寫入、數據庫寫入等。

總結一下:

  • 事務性操作可以開啟線程的事件循環,將操作分攤到子線程。
  • 工程開發中,多數情況不會開啟線程的事件循環。
  • 線程多用于執行后臺任務或者耗時任務。

3 信號與槽的連接方式

深入信號與槽的連接方式:

小知識:

知識回顧:

3.1 Qt::DirectConnection(立即調用)

直接在發送信號的線程中調用槽函數,等價于槽函數的實時調用!

void direct_connection() {static TestThread t;static MyObject m;QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::DirectConnection);t.start();t.wait(5 * 1000);t.quit(); }

輸入結果:

3.2 Qt::QueuedConnection(異步調用)

信號發送至目標線程的事件隊列,由目標線程處理,當前線程繼續向下執行!

void queued_connection() {static TestThread t;static MyObject m;QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::QueuedConnection);t.start();t.wait(5 * 1000);t.quit(); }

結果:

3.3 Qt::BlockingQueuedConnection(同步調用)

信號發送至目標線程的事件隊列,由目標線程處理,當前線程等待槽函數返回,之后繼續向下執行!

注意:目標線程和當前線程必須不同!

void blocking_queued_connection() {static TestThread t;static MyObject m;QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::BlockingQueuedConnection);t.start();t.wait(5 * 1000);t.quit(); }

輸出結果:

這里的輸出結果值得分析下:

  • 我們可以看到雖然執行了線程的quit函數但是線程并沒有退出事件循環,而是等待槽函數執行完畢才退出事件循環。

3.4 Qt::AutoConnection(默認連接)


圖中略有錯誤,只有當發送線程依附的線程等于接收線程時才相當于立即調用,否則還是異步調用,會放到事件隊列中去。

void auto_connection() {static TestThread t;static MyObject m;QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));t.start();t.wait(5 * 1000);t.quit(); }

輸出結果:

3.5 Qt::UniqueConnection(單一連接)

描述:

  • 功能與AutoConnection相同,自動確定連接類型。
  • 同一個信號與同一個參函數之間只有一個連接。

小知識:

  • 默認情況下,同一個信號可以多次連接到同一個槽函數。
  • 多次連接意味著同一個槽函數的多次調用。
void unique_connection() {static TestThread t;static MyObject m;QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::UniqueConnection);QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::UniqueConnection);t.start();t.wait(5 * 1000);t.quit(); }

輸出結果:


參考資料:

  • QT實驗分析教程
  • 總結

    以上是生活随笔為你收集整理的Qt多线程中的信号与槽的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。