? ? ? 信号和槽是Qt的核心机制之一,通过该机制大大简化了开发者的开发难度。信号和槽属于观察者模式(本质上是回调函数的应用)。是函数就需要考虑其是在那个线程中执行,本文讨论的就是槽函数在那个线程中执行的问题。
目录
? ? ? ?connect函数用来连接信号和槽,类似于提前注册。其第五个参数默认是Qt::AutoConnection,所以开发者很多时候可以忽略此参数。但是在牵扯到复杂业务开发,尤其是多线程并发开发时往往需要关注第五个参数,第五个参数取值和含义如下:
????自动连接,发送者和接受者在同一个线程时等同于Qt::DirectConnection,不同线程等同于Qt::QueuedConnection
? ? 直接(同步)连接,槽函数在接受者所依附线程执行。
? ? 队列(异步)连接,槽函数在发送信号的线程执行。异步连接的时候,信号是由接收者所在的线程的事件处理机制来处理的,如果接收者所在的线程没有事件处理的话,这个信号就不会被处理
? ? 阻塞连接,发送者和接收者在同一线程时会死锁
? ? 唯一连接,防止信号和槽重复连接
? ? 单次连接,信号和槽函数只连接一次,槽函数执行后连接会自动断开
发送者
sender.h
#ifndef SENDER_H
#define SENDER_H
#include <QObject>
#include <QString>
class Sender :public QObject {
Q_OBJECT
public:
Sender(QObject* parent = 0);
public slots :
void emitsig(const QString& str, const int& ci);
signals:
void sig(const QString& str, const int& ci);
};
#endif // SENDER_H
sender.cpp
#include "sender.h"
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
Sender::Sender(QObject* parent) : QObject(parent) {
}
void Sender::emitsig(const QString& str, const int& ci)
{
qDebug() << "sender signal thread id is: " << QThread::currentThreadId()
<< str << ci;
emit sig(str, ci);
}
接收者
recver.h
#ifndef RECVER_H
#define RECVER_H
#include <QObject>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
class Recver : public QObject
{
Q_OBJECT
public:
explicit Recver(QObject *parent = nullptr);
public slots :
void slot(const QString& str, const int& ci);
};
#endif // RECVER_H
recver.cpp
#include "recver.h"
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
Recver::Recver(QObject *parent)
: QObject{parent}
{
}
void Recver::slot(const QString& str, const int& ci) {
qDebug() << "recver slot thread id is: " << QThread::currentThreadId()
<< str << ci;
}
main函数
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
#include "sender.h"
#include "recver.h"
#include "mythread.h"
/*
// QObject::connect函数的第五个参数:
Qt::AutoConnection:自动连接,发送者和接受者在同一个线程时等同于Qt::DirectConnection,不同线程等同于Qt::QueuedConnection
Qt::DirectConnection:直接调用连接,槽函数在接受者所依附线程执行。
Qt::QueuedConnection:异步调用连接,槽函数在发送信号的线程执行。异步连接的时候,信号是由接收者所在的线程的事件处理机制来处理的,如果接收者所在的线程没有事件处理的话,这个信号就不会被处理
Qt::BlockingQueuedConnection:阻塞连接调用,发送者和接收者在同一线程时会死锁
Qt::UniqueConnection:防止信号和槽重复连接
Qt::SingleShotConnection:信号和槽函数仅只需单次连接,槽函数执行后连接会自动断开
*/
/*
// Qt4和Qt5都适用的连接方式,注意:此方式是在Q5上可能会导致槽函数不被调用,此时可尝试使用Qt5新的连接方式
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::DirectConnection);
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::QueuedConnection);
//QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::BlockingQueuedConnection);
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::UniqueConnection);
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::SingleShotConnection);
*/
/*
// Qt5最新的连接方式
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::DirectConnection);
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::QueuedConnection);
// 发送者和接收者在同一个线程,将会死锁
//QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::BlockingQueuedConnection);
//QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::UniqueConnection);
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::SingleShotConnection);
*/
// 主线程获取
/*
QCoreApplication::instance()->thread();
*/
/*
// 关于Qt线程的使用说明
在Qt4.3(包括)之前,run 是虚函数,必须子类化QThread来实现run函数。
而从Qt4.4开始,qthreads-no-longer-abstract,run 默认调用 QThread::exec() 。这样一来不需要子类化 QThread 了,只需要子类化一个 QObject就够了
QThread中run对于线程的作用相当于main函数对于应用程序。它是线程的入口,run的开始和结束意味着线程的开始和结束。
QThread所依附的线程,就是创建线程的线程。
QThread管理的线程,就是run中创建的线程。
*/
// 连接多次,发送信号槽函数也会多次触发
void test1() {
Sender sender;
Recver recver;
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);
qDebug() << "call first";
sender.emitsig("fist", 110);
qDebug() << "call second";
sender.emitsig("second", 220);
qDebug() << "call third";
sender.emitsig("third", 330);
}
// 接受对象虽然是线程对象,但是槽函数却在发送对象所在线程对象执行,因为接收者依附于发送对象所在线程
/*
QThread中slot和run函数共同操作的对象,都会用QMutex锁住是因为slot和run处于不同线程,需要线程间的同步。
*/
void test2() {
Sender sender;
Mythread mythread;
// Mythread构造函数中去掉moveToThread(this);,槽函数将在主线程执行,加上moveToThread(this)槽函数将在子线程执行
// 加上moveToThread(this)后连接方式修改为Qt::DirectConnection,槽函数在主线程中执行
QObject::connect(&sender, SIGNAL(sig(const QString&, const int&)), &mythread, SLOT(slot_main(const QString&, const int&)));
mythread.start();
sender.emitsig("mythread", 440);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thead id is : " << QThread::currentThreadId();
//std::cout << std::this_thread::get_id() << std::endl;
Sender sender;
Recver recver;
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);
sender.emitsig("fist", 110);
return a.exec();
}
运行效果:
可以看到槽函数在信号发送者所在线程(主线程)中执行。
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
#include "sender.h"
#include "recver.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thead id is : " << QThread::currentThreadId();
//std::cout << std::this_thread::get_id() << std::endl;
Sender sender;
Recver recver;
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);
QThread thread;
recver.moveToThread(&thread);
thread.start();
sender.emitsig("fist", 110);
return a.exec();
}
运行效果:
代码中创建一个子线程对象,并将接收者移动到子线程,槽函数在子线程中执行。
mythread.h
#ifndef MYTHRED_H
#define MYTHRED_H
#include <QThread >
class Mythread : public QThread
{
Q_OBJECT
public:
Mythread(QObject* parent = 0);
public slots:
void slot_main(const QString& str, const int& ci);
protected:
void run();
};
#endif // MYTHRED_H
mythread.cpp
#include "mythread.h"
#include <QDebug>
#include "sender.h"
Mythread::Mythread(QObject* parent) : QThread(parent)
{
//moveToThread(this);
}
void Mythread::slot_main(const QString& str, const int& ci) {
qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}
void Mythread::run() {
qDebug() << "mythread thread: " << currentThreadId();
Sender sender;
connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)));
sender.emitsig("thread", 220);
exec();
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
#include "sender.h"
#include "recver.h"
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thead id is : " << QThread::currentThreadId();
//std::cout << std::this_thread::get_id() << std::endl;
Mythread mythread;
mythread.start();
return a.exec();
}
运行效果如下:
如上显示在子线程发送信号,槽函数却在主线程中执行,因为接收者mythread是在主线程中创建。
mythread.cpp
#include "mythread.h"
#include <QDebug>
#include "sender.h"
Mythread::Mythread(QObject* parent) : QThread(parent)
{
moveToThread(this);
}
void Mythread::slot_main(const QString& str, const int& ci) {
qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}
void Mythread::run() {
qDebug() << "mythread thread: " << currentThreadId();
Sender sender;
connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)));
sender.emitsig("thread", 220);
exec();
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
#include "sender.h"
#include "recver.h"
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thead id is : " << QThread::currentThreadId();
//std::cout << std::this_thread::get_id() << std::endl;
Mythread mythread;
mythread.start();
return a.exec();
}
运行效果:
? ? ? 将信号接收者构造函数//moveToThread(this);的注释放开,槽函数在子线程中执行。尽管接收者在主线程中被创建。
如果构造函数注释moveToThread(this);,connect第五个参数修改为Qt::DirectConnection,槽函数也可以在子线程中执行,如下:
connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)), Qt::DirectConnection);
mythread.cpp
#include "mythread.h"
#include <QDebug>
#include "sender.h"
Mythread::Mythread(QObject* parent) : QThread(parent)
{
//moveToThread(this);
}
void Mythread::slot_main(const QString& str, const int& ci) {
qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}
void Mythread::run() {
qDebug() << "mythread thread: " << currentThreadId();
Sender sender;
connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)), Qt::DirectConnection);
sender.emitsig("thread", 220);
exec();
}
信号发送者在子线程,槽函数也在子线程中执行。
? ? ? ?有很多人分不清发送者所在线程和发送信号的线程,在此做一下区分说明。发送者所在线程指的是构造发送者对象所在的线程,发送信号线程指的是发送信号所在的线程,即调用emit所在的线程,有些时候这两个线程并不是一个线程。前面说的槽函数在发送者所在线程执行指的是在发送者所在线程,而不是发送信号的线程。如下代码:
mythread.h
#ifndef MYTHRED_H
#define MYTHRED_H
#include <QThread>
#include "sender.h"
class Mythread : public QThread
{
Q_OBJECT
public:
Mythread(QObject* parent = 0);
Mythread(Sender* sender);
public slots:
void slot_main(const QString& str, const int& ci);
protected:
void run();
private:
Sender* m_sender;
};
#endif // MYTHRED_H
mythread.cpp
#include "mythread.h"
#include <QDebug>
Mythread::Mythread(QObject* parent) : QThread(parent)
{
//moveToThread(this);
m_sender = nullptr;
}
Mythread::Mythread(Sender* sender) {
//moveToThread(this);
m_sender = sender;
}
void Mythread::slot_main(const QString& str, const int& ci) {
qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}
void Mythread::run() {
qDebug() << "mythread thread: " << currentThreadId();
if (m_sender) {
connect(m_sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)));
m_sender->emitsig("thread", 220);
}
exec();
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>
#include "sender.h"
#include "recver.h"
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thead id is : " << QThread::currentThreadId();
//std::cout << std::this_thread::get_id() << std::endl;
Sender sender;
Mythread mythread(&sender);
mythread.start();
return a.exec();
}
运行效果如下:
? ? ? ?如上,发送者对象在主线程创建,在子线程中发送信号,槽函数是在主线程中执行,而并非在子线程中执行。