理解QThread 

理解和使用 QThread

QThread实现相关

  • Qt 4.3(包括)之前,QThread::run()纯虚函数,必须子类化QThread并实现run()函数。

    官方例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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();
    }

    QThread实例归属于初始化它的主线程(或者其他工作线程),而不是运行它的run()函数的新线程, 这意味着 QThread 的所有queued connections连接类型的槽函数和invoked methods 都将在旧线程(QThread实例的归属线程)执行。

    因此,希望在新线程中调用槽函数的开发人员必须使用Qt 4.4之后引入的worker-object方法; 不应将新插槽直接实现到QThread的子类中。

    queued connections连接类型的槽函数或invoked methods不同,直接调用 QThread 对象的方法将在调用该方法的线程(也就是QThread的归属线程)中执行。

    当子类化 QThread 时,请记住构造函数在旧线程中执行,而 run() 在新线程中执行。 在QThread的成员函数和在run()函数中访问QThread的成员变量时,实际上是从两个不同的线程访问该变量。 必须检查同时访问的安全性,确认是否需要加互斥锁。

    注意:

    跨线程与对象交互时必须小心。

    作为一般规则,只能从创建 QThread 对象本身的线程调用QThread的成员函数(例如 setPriority()),除非 文档另有说明。

    有关详细信息,请参阅同步线程

  • Qt 4.4开始,QThread::run() 默认调用 QThread::exec()

    这样就子类化 QThread 就不是必须的了,只需要一个继承自QObject的子类实现线程函数,并将该子类实例使用moveToThread()移动到线程对象中,并将线程函数Connect到线程对象的operator ()就可以了。

    这是 Bradley T. Hughes推荐的方法。

    官方例子:

    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
    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;
    public:
    Controller() {
    Worker *worker = new Worker;
    worker->moveToThread(&workerThread);
    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的槽函数连接到其他任意线程中的任意对象的随便什么信号上,跨线程连接信号和槽函数是安全的,由queued connections机制保证。

线程管理

QThread提供了两个信号started() 和 finished()用来通知用户线程已经启动或停止, 也可以用成员函数isFinished() 和 isRunning()主动查询线程的状态.

调用 exit() 或者 quit()可以停止线程,极端情况下还可以调用 terminate()直接终止正在运行的线程, 但是并不鼓励这么做。

Qt 4.8之后,通过连接QThread::finished()信号和QObject::deleteLater()槽函数,可以在线程结束的时候删除其中存活的对象。

wait() 阻塞调用线程,直到wait()的目标线程结束。

QThread 提供的sleep函数(静态、平台无关):sleep()、 msleep()、usleep() 。

备注: 因为Qt是事件驱动的,所以wait() 和 sleep() 函数不是必需的。监听finished()信号可以替代 wait(),sleep() 也可以用 QTimer替代。

QThread类静态函数 currentThreadId() 和currentThread()返回当前运行线程的标识符。 前者返回平台相关的线程ID; 后者返回QThread 对象指针。

例程

定义一个普通的QObject派生类,然后将其对象move到QThread中。

使用信号和槽时不用考虑多线程的存在。

也不用使用QMutex来进行同步,Qt的事件循环会自动处理好。

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
// worker.h
#pragma once

#include <QObject>
#include <QThread>

struct EntryLogObj {
EntryLogObj(const char *f);
~EntryLogObj();
const char *f_;
};

#define LOG_ENTRY_OBJ EntryLogObj obj_entry##__LINE__(__FUNCTION__)


// 主线程中创建Worker对象;
// Worker对象moveToThread到工作线程后,槽函数在工作线程中运行;
class Worker: public QObject
{
Q_OBJECT
public:
Worker();
~Worker();
signals:
// 从工作线程发射的信号;
void signal_from_worker(const QString &param);
public slots:
// 工作线程中执行该槽函数;
void slot_func(const QString &param) ;
void on_work_thread_started();
void on_work_thread_finished();
};

// 主线程中创建Controller对象;
// Controller的槽函数在主线程中运行;
class Controller : public QObject
{
Q_OBJECT
QThread work_thread;
public:
Controller();
~Controller();
void start();
void stop();
void operate();
public slots:
void handleResults(const QString &param);
void on_work_thread_started();
void on_work_thread_finished();
signals:
void do_work(const QString &param);
};
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// worker.cpp
#include <worker.h>

EntryLogObj::EntryLogObj(const char *f)
: f_(f)
{
#ifdef _DEBUG
printf("[%08lld] ------> %s\n", quintptr(QThread::currentThreadId()), f_);
#endif
}
EntryLogObj::~EntryLogObj()
{
#ifdef _DEBUG
printf("[%08lld] <------ %s\n", quintptr(QThread::currentThreadId()), f_);
#endif
}

Worker::Worker()
{
LOG_ENTRY_OBJ;
}
Worker::~Worker()
{
LOG_ENTRY_OBJ;
}

void Worker::slot_func(const QString &param)
{
LOG_ENTRY_OBJ;
emit signal_from_worker(__func__);
}

void Worker::on_work_thread_started()
{
LOG_ENTRY_OBJ;
}
void Worker::on_work_thread_finished()
{
LOG_ENTRY_OBJ;
}

Controller::Controller()
{
LOG_ENTRY_OBJ;
}
Controller::~Controller()
{
LOG_ENTRY_OBJ;
stop();
}
void Controller::start()
{
LOG_ENTRY_OBJ;
Worker *worker = new Worker;
worker->moveToThread(&work_thread);

connect(&work_thread, &QThread::started, []() {
LOG_ENTRY_OBJ;
printf("[%08lld] lamda for signal started of work thread.\n", quintptr(QThread::currentThreadId()));
});
connect(&work_thread, &QThread::finished, []() {
LOG_ENTRY_OBJ;
printf("[%08lld] lamda for signal finished of work thread.\n", quintptr(QThread::currentThreadId()));
});

connect(&work_thread, &QThread::started, this, &Controller::on_work_thread_started);
connect(&work_thread, &QThread::finished, this, &Controller::on_work_thread_finished);

connect(&work_thread, &QThread::started, worker, &Worker::on_work_thread_started);
connect(&work_thread, &QThread::finished, worker, &Worker::on_work_thread_finished);

connect(&work_thread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::do_work, worker, &Worker::slot_func);
connect(worker, &Worker::signal_from_worker, this, &Controller::handleResults);

work_thread.start();
}
void Controller::stop()
{
LOG_ENTRY_OBJ;
if (work_thread.isRunning()) {
work_thread.quit();
work_thread.wait();
}
}
void Controller::operate()
{
LOG_ENTRY_OBJ;
emit do_work(__func__);
}
void Controller::handleResults(const QString &param)
{
LOG_ENTRY_OBJ;
}
void Controller::on_work_thread_started()
{
LOG_ENTRY_OBJ;
}
void Controller::on_work_thread_finished()
{
LOG_ENTRY_OBJ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// main.cpp
#include <QCoreApplication>
#include <QThread>

#include <worker.h>
#include <QTimer>

int main(int argc, char *argv[])
{
setvbuf(stdout, NULL, _IONBF, 0);
LOG_ENTRY_OBJ;

QCoreApplication a(argc, argv);

Controller c;
c.start();
c.operate();
//c.stop();
QTimer::singleShot(10000, [&]() {
LOG_ENTRY_OBJ;
c.stop();
});
return a.exec();
}
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
58
# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0074 NEW)

project(qthread-test LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

function(assign_source_group)
foreach(_source IN ITEMS ${ARGN})
if (IS_ABSOLUTE "${_source}")
file(RELATIVE_PATH _source_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source}")
else()
set(_source_rel "${_source}")
endif()
get_filename_component(_source_path "${_source_rel}" PATH)
string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
source_group("${_source_path_msvc}" FILES "${_source}")
endforeach()
endfunction(assign_source_group)

function(my_add_executable)
foreach(_source IN ITEMS ${ARGN})
assign_source_group(${_source})
endforeach()
add_executable(${ARGV})
endfunction(my_add_executable)

file(GLOB_RECURSE JSON_CODEC_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
file(GLOB_RECURSE JSON_CODEC_CPPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
my_add_executable(qthread-test
${JSON_CODEC_HEADERS}
${JSON_CODEC_CPPS}
)
# set qthread-test as startup project;
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT qthread-test)

target_include_directories(qthread-test
PUBLIC include
)
target_compile_options(qthread-test
PUBLIC $<$<CXX_COMPILER_ID:MSVC>:/bigobj>
)

target_link_libraries(qthread-test Qt${QT_VERSION_MAJOR}::Core)

执行输出结果:

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
#thread_id ------- function
[00006080] ------> main
# 主线程中构造Controller对象c
[00006080] ------> Controller::Controller
[00006080] <------ Controller::Controller
# 主线程中调用c.start()开始执行
[00006080] ------> Controller::start
# 由于c.start()在主线程中调用,所以worker对象也在主线程中完成构造
[00006080] ------> Worker::Worker
[00006080] <------ Worker::Worker
# c.start()执行完成
[00006080] <------ Controller::start
# 主线程中调用c.operate()
[00006080] ------> Controller::operate
[00006080] <------ Controller::operate
# 和QThread::started信号连接的槽函数是lamda表达式时,lamda表达式在工作线程中执行
[00014128] ------> Controller::start::<lambda_aaec42b3d34584b5fbfc15e802205f36>::operator ()
[00014128] lamda for signal started of work thread.
[00014128] <------ Controller::start::<lambda_aaec42b3d34584b5fbfc15e802205f36>::operator ()
# 和QThread::started信号连接的槽函数是worker的成员函数时,槽函数在工作线程中执行
[00014128] ------> Worker::on_work_thread_started
[00014128] <------ Worker::on_work_thread_started
# 和QThread::started信号连接的槽函数是Controller的成员函数时,槽函数在主线程中执行
[00006080] ------> Controller::on_work_thread_started
[00006080] <------ Controller::on_work_thread_started
# 前面主线程调用c.operate()后,发送信号do_work,相连接的槽函数Worker::slot_func此时在子线程中被执行
[00014128] ------> Worker::slot_func
[00014128] <------ Worker::slot_func
# 在Worker::slot_func槽函数中,发送信号signal_from_worker,相连接的槽函数Controller::handleResults此时在主线程中被执行
[00006080] ------> Controller::handleResults
[00006080] <------ Controller::handleResults
# 定时器10秒后, 定时器连接的槽函数是lamda表达式,lamda表达式在主线程中被执行
[00006080] ------> main::<lambda_83095c63ae8e5fc2fe78e36be3ad6bf3>::operator ()
# lamda表达式中调用c.stop(),同样在主线程中执行;
[00006080] ------> Controller::stop
# 和QThread::finished信号连接的槽函数是lamda表达式时,lamda表达式在工作线程中执行
[00014128] ------> Controller::start::<lambda_f84ef892ea55e63633fd4e3ce35a0984>::operator ()
[00014128] lamda for signal finished of work thread.
[00014128] <------ Controller::start::<lambda_f84ef892ea55e63633fd4e3ce35a0984>::operator ()
# 和QThread::finished信号连接的槽函数是worker的成员函数时,槽函数在工作线程中执行
[00014128] ------> Worker::on_work_thread_finished
[00014128] <------ Worker::on_work_thread_finished
# Worker对象在QThread::finished信号连接的QObject::deleteLater时被删除,在工作线程中完成删除对象动作
[00014128] ------> Worker::~Worker
[00014128] <------ Worker::~Worker
# 主线程完成c.stop()调用
[00006080] <------ Controller::stop
[00006080] <------ main::<lambda_83095c63ae8e5fc2fe78e36be3ad6bf3>::operator ()
# 和QThread::finished信号连接的槽函数是Controller的成员函数时,槽函数在主线程中执行
[00006080] ------> Controller::on_work_thread_finished
[00006080] <------ Controller::on_work_thread_finished

Reference