显示目录

线程的上下文

Qt 中每个 QObject 及其子类的对象都有自己的线程上下文环境,即对象所属的线程,对象属于创建它的函数执行时所在的线程,例如在 Ui 线程中的函数里创建了对象 ops,则 ops 属于 Ui 线程。使用 QObject::thread() 获取对象所属线程,可以使用 QObject::moveToThread(otherThread) 移动一个对象到另一个线程。由于跨线程调用函数有可能会造成程序崩溃,所以有比较了解代码在执行时它所处的线程。

下面以示例演示不同情况下代码执行时所处的线程:

  • Widget 所属线程
  • 线程对象所属线程
  • Lambda 的方式处理信号槽
  • Qt 5 函数指针的方式处理信号槽
  • 传统 signal slot 的方式处理信号槽

注意: 下面的注释都是基于示例代码的,为了不让描述的太过繁琐,便于帮助理解,有些描述理论上可能是不精确的,需要大家自行更进一步的分析。

涉及到的文件有:

  • Widget.h
  • Widget.cpp
  • Thread.h
  • Thread.cpp

类 Widget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Thread;

class Widget : public QWidget {
Q_OBJECT

public:
explicit Widget(QWidget *parent = nullptr);
~Widget();

Q_INVOKABLE void directCall();
Q_INVOKABLE void invokeCall();
Q_INVOKABLE void slotCall();

private:
Thread *thread;
};

#endif // WIDGET_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "Widget.h"
#include "Thread.h"
#include <QDebug>
#include <QMetaObject>

// [+] 表示属于 Ui 线程
// [-] 表示属于 Thread 线程
Widget::Widget(QWidget *parent) : QWidget(parent) {
qDebug() << "1-Widget::Widget(): " << QThread::currentThread(); // [+] 属于 Ui 线程
thread = new Thread(); // 创建线程对象
qDebug() << "3-Widget.thread: " << thread->thread(); // [+] 属于 Ui 线程: thread 属于创建它的线程
this->directCall(); // [+] 属于 Ui 线程: 被调用的函数中

// 事件处理 1
connect(thread, &Thread::beat, [this]() {
qDebug() << "Lambda-1: " << QThread::currentThread(); // [-] 属于 Thread 的线程: 当前函数中
this->directCall(); // [-] 属于 Thread 的线程: 被调用的函数中
QMetaObject::invokeMethod(this, "invokeCall"); // [+] 属于 Ui 线程: 被调用的函数中
});

// 事件处理 2
// 第 3 个参数 context 为 this,指定了 Lambda 里的线程为 this 所属线程,即 Ui 线程
connect(thread, &Thread::beat, this, [this]() {
qDebug() << "Lambda-2: " << QThread::currentThread(); // [+] 属于 Ui 线程: 当前函数中
this->directCall(); // [+] 属于 Ui 线程: 被调用的函数中
QMetaObject::invokeMethod(this, "invokeCall"); // [+] 属于 Ui 线程: 被调用的函数中
});

// 事件处理 3
connect(thread, &Thread::beat, this, &Widget::slotCall); // [+] 属于 Ui 线程: 被调用的函数中

// 事件处理 4
connect(thread, SIGNAL(beat()), this, SLOT(slotCall())); // [+] 属于 Ui 线程: 被调用的函数中

thread->start();
}

Widget::~Widget() {}

void Widget::directCall() {
// [+] 属于 Ui 线程
// [-] 属于 Thread 线程
// 直接调用的函数属于调用它时代码所在的线程,不管它是谁的函数
qDebug() << "directCall: " << QThread::currentThread();
}

void Widget::invokeCall() {
// [+] 属于 Ui 线程
// invokeMethod() 调用的函数属于它的对象所在的线程,不管它在哪个线程中
qDebug() << "invokeCall: " << QThread::currentThread();
}

void Widget::slotCall() {
// [+] 属于 Ui 线程
// 传统 signal slot 调用的函数属于 receiver 说在的线程
qDebug() << "slotCall: " << QThread::currentThread();
}

类 Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Thread.h
#ifndef THREAD_H
#define THREAD_H

#include <QThread>

class Thread : public QThread {
Q_OBJECT
public:
Thread();

protected:
void run() override;

signals:
void beat();
};

#endif // THREAD_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Thread.cpp
#include "Thread.h"
#include <QDateTime>
#include <QDebug>

Thread::Thread() {
qDebug() << "2-Thread::Thread(): " << QThread::currentThread(); // [+] 属于 Ui 线程
}

void Thread::run() {
// [-] 属于 Thread 的线程
// 虽然线程对象 thread 属于 Ui 线程,但是它的 run 函数属于 Thread 自己的线程
qDebug() << "4-Thread.run(): " << QThread::currentThread();

while (true) {
emit beat();
QThread::msleep(2000);
}
}

运行

运行程序,输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
1-Widget:: Widget(): QThread(0x7fdde040e5e0)
2-Thread:: Thread(): QThread(0x7fdde040e5e0)
3-Widget.thread : QThread(0x7fdde040e5e0)
directCall : QThread(0x7fdde040e5e0)
4-Thread.run() : Thread(0x7fdde040f070)
Lambda-1 : Thread(0x7fdde040f070)
directCall : Thread(0x7fdde040f070)
invokeCall : QThread(0x7fdde040e5e0)
Lambda-2 : QThread(0x7fdde040e5e0)
directCall : QThread(0x7fdde040e5e0)
invokeCall : QThread(0x7fdde040e5e0)
slotCall : QThread(0x7fdde040e5e0)
slotCall : QThread(0x7fdde040e5e0)

根据输出结果,参考代码,结论如下 (这里的 connection type 使用 Qt::AutoConnection):

  • Widget 的构造函数属于 Ui 线程,在此构造函数里面创建了 Thread 的对象 thread,所以 thread 属于 Ui 线程

  • 线程对象 thread 的 run() 函数属于 Thread 自己的线程,而不是 Ui 线程

  • 直接调用的函数属于调用它时代码所在的线程,不管它是谁的函数

  • 使用 invokeMethod() 调用的函数属于它的对象所在的线程,不管它在哪个线程中被调用

  • 使用 Lambda 的方式处理信号槽,如果 connect 的第三个参数是一个指针对象,即线程的 context,则 Lambda 函数执行时的线程上下文为 context 所属的线程,否则属于线程对象 thread,connect 的函数签名如下:

    1
    2
    QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
    QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
  • 传统 signal slot 调用的函数属于 receiver 所属的线程