首页 > 代码库 > 第78课 多线程中的信号与槽(上)
第78课 多线程中的信号与槽(上)
1. QThread类中的信号和槽
(1)QThread类拥有发射信号和定义槽函数的能力
(2)QThread中的关键信号
①void started():线程开始运行时发射该信号
②void finished():线程完成运行时发射该信号
③void terminated():线程被异常终止时发射该信号
2. 与线程相关的概念
(1)线程栈
①进程中存在栈空间的概念(区别于栈数据结构)
②栈空间专用于函数调用(保存函数参数、局部变量等)
③线程拥有独立的栈空间(可调用其它函数)
④只要函数体中没有访问临界资源的代码,同一个函数可以被多个线程同时调用,且不会产生任何副作用。
(2)进程PID和线程TID
①进程PID:进程拥有全局唯一的ID值。qint64 QProcess::processId() const
②线程TID:线程有进程内唯一的ID值。
(3)QThread中的关键静态成员函数
①Qt::HANDLE currentThreadId()
②QThread* currentThread()
3. 多个线程,槽函数是在哪个线程中执行的?
(1)自动连接:如果发射信号的线程及槽函数对象所在线程是同一线程,默认采用直接连接。如果位于不同线程,则采用队列连接。
(2)直接连接(Direct Connection):说明槽函数要被立即、直接地调用(可理解为采用send的方式)。所以,谁发射信号就由谁来接收信号(执行槽函数),即发射信号的线程即要发射信号,也要执行槽函数。
(3)队列连接(Queued Connection):说明信号被投递(post)到槽函数对象所在线程的事件队列中。因此理所当然地,槽函数应由其所在对象的线程执行。
【编程实验】槽函数的运行上下文:多个线程,槽函数是在哪个线程中执行的?
//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(); //响应线程结束的槽函数 }; #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(); }
//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); ~TestThread(); signals: void testSignal(); protected slots: void testSlot(); }; #endif // TESTTHREAD_H
//TestThread.cpp
#include <QDebug> #include "TestThread.h" 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(); //自定义信号由子线程发射,本例中为信号和槽为默认连接,因槽函数对象 //在主线程中,是两个不同的线程,采用队列连接。所以槽函数由主线程执行。 qDebug() << "void TestThread::run() -- end"; } void TestThread::testSlot() { qDebug() << "void TestThread::testSlot() tid = " << currentThreadId(); } TestThread::~TestThread() { }
//main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QDebug> #include "MyObject.h" #include "TestThread.h" //自定义消息处理程序,让QDebug输出是线程安全的。 void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QByteArray localMsg = msg.toLocal8Bit(); //fcntl(stderr, ); static QMutex mutex; mutex.lock(); switch (type) { case QtDebugMsg: fprintf(stderr, "%s\n", localMsg.constData()); break; case QtInfoMsg: fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtWarningMsg: fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtCriticalMsg: fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtFatalMsg: fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); abort(); } mutex.unlock(); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 安装消息处理程序 qInstallMessageHandler(messageOutput); qDebug() << "main() tid = " << QThread::currentThreadId(); TestThread t; MyObject m; //m.moveToThread(&t); //这里可以取消注释来演示执行getStarted和getFinished()两个 //槽函数的线程的差别 //默认连接:当m.moveToThread(&t)时,m与发射信号的子线程是同一线程,采用直接连接。 //当不执行m.moveToThread时,发射信息线程与m所在线程不是同一线程,所以采用队列连接。 //QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted())); //QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished())); //队列连接:说明信号被投递到槽函数所在对象线程消息队列中的 //本例中发射信号线程(子线程)将信号投到槽函数对象(m)所在线程 QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()), Qt::QueuedConnection); //直接连接:说明槽函数是被直接调用的。所以由谁发射就由谁来接收(执行槽函数)。本例由子线程发射,也应由子线程接收) QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished()),Qt::DirectConnection); t.start(); return a.exec(); } /*输出结果: main() tid = 0x428 void TestThread::run() -- begin tid = 0x2280 void MyObject::getStarted() tid = 0x428 void TestThread::run() i = 0 void TestThread::run() i = 1 void TestThread::run() i = 2 void TestThread::run() i = 3 void TestThread::run() i = 4 void TestThread::run() i = 5 void TestThread::run() i = 6 void TestThread::run() i = 7 void TestThread::run() i = 8 void TestThread::run() i = 9 void TestThread::run() -- end void TestThread::testSlot() tid = 0x428 void MyObject::getFinished() tid = 0x2280 */
4. 小结
(1)QThread类拥有发射信号和定义槽函数的能力
(2)线程在进程内拥有一个唯一的ID值
(3)线程拥有独立的栈空间用于函数调用
(4)没有临界资源的函数可以无副作用的被多个线程调用
(5)槽函数的调用在某一个线程中完成。
第78课 多线程中的信号与槽(上)