在工作线程中创建的QObject的线程亲和性会发生什么变化呢?

Ale*_*kiy 23 c++ qt multithreading qtconcurrent

假设我调用QtConcurrent::run()哪个在工作线程中运行一个函数,并在该函数中动态分配几个QObject(供以后使用).由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联.但是,一旦工作线程终止,QObject线程关联就不再有效.

问题:Qt是自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?

Okt*_*ist 6

QThread没有记录QObject在它完成时自动移动任何s,所以我认为我们已经可以得出结论它没有这样的事情.这种行为会非常令人惊讶,与API的其他部分不一致.

为了完整起见,我测试了Qt 5.6:

QObject o;
{
    QThread t;
    o.moveToThread(&t);
    for (int i = 0; i < 2; ++i)
    {
        t.start();
        QVERIFY(t.isRunning());
        QVERIFY(o.thread() == &t);
        t.quit();
        t.wait();
        QVERIFY(t.isFinished());
        QVERIFY(o.thread() == &t);
    }
}
QVERIFY(o.thread() == nullptr);
Run Code Online (Sandbox Code Playgroud)

回想一下,a QThread不是一个线程,它管理一个线程.

QThread完成后,它继续存在,以及生活在其中的对象继续住在这里,但他们不再处理事件.将QThread可以重新启动(不推荐),此时事件处理将恢复(所以同样QThread然后可以管理不同的线程).

当a QThread被销毁时,生活在其中的对象不再具有任何线程亲和力.该文档不保证这一点,其实说"你必须确保在一个线程创建的所有对象在删除之前被删除QThread."


假设我调用QtConcurrent::run()哪个在工作线程中运行一个函数,并在该函数中动态分配几个QObject(供以后使用).由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联.但是,一旦工作线程终止,QObject线程关联就不再有效.

QThread不会在这种情况下终止.当一个任务通过QtConcurrent::run完成而产生时,QThread它所运行的任务将被返回到QThreadPool并且可以通过随后的调用重新使用QtConcurrent::run,并且QObject生活在那里QThread继续生活.

QThreadPool::globalInstance()->setMaxThreadCount(1);
QObject *o = nullptr;
QThread *t = nullptr;
QFuture<void> f = QtConcurrent::run([&] {
    o = new QObject;
    t = o->thread();
    QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
QVERIFY(t == o->thread());
QVERIFY(t->isRunning());
f = QtConcurrent::run([=] {
    QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
Run Code Online (Sandbox Code Playgroud)

您可能希望在将对象QThread返回到之前手动移动对象QThreadPool,或者只是不要使用QtConcurrent::run.拥有一个比任务更长的QtConcurrent::run任务构造QObject是一个有问题的设计,任务应该是自包含的.如@Mike所述,使用的QThreads QtConcurrent::run没有事件循环.


Mik*_*ike 5

但是,一旦工作线程终止,QObject线程关联就不再有效.

工作线程不会你的函数调用之后终止.整个使用点QtConcurrent::run是在全局线程池(或一些提供的QThreadPool)上执行大量小任务,同时重用线程以避免为这些小任务中的每一个创建和销毁线程的开销.除了在所有可用内核之间分配计算.

您可以尝试查看Qt的源代码以了解如何QtConcurrent::run实现.你会看到它最终调用RunFunctionTaskBase::start,它本质上调用QThreadPool::start一个QRunnable调用最初传递给的函数QtConcurrent::run.

现在,我想的一点是,QThreadPool::start实现通过添加QRunnable到队列,然后试图唤醒线程池中的线程(这是一个等待QRunnable添加到队列).这里需要注意的是,来自线程池的线程没有运行事件循环(它们不是按照这种方式设计的),它们只是QRunnable在队列中执行s而已经没有了(它们是以这种方式实现的表现原因很明显).

这意味着,当您QObject在执行的函数中创建一个时QtConcurrent::run,您只是创建一个QObject生活在没有事件循环的线程中,从文档中,限制包括:

如果没有运行事件循环,则不会将事件传递给对象.例如,如果您QTimer在线程中创建一个对象但从不调用exec(),则QTimer它将永远不会发出其timeout()信号.呼叫deleteLater()也不起作用.(这些限制也适用于主线程.)


TL; DR: QtConcurrent::run在全局QThreadPool(或提供的)线程中运行函数.这些线程不运行事件循环,它们只是等待QRunnables运行.因此,QObject生活在这些线程的线程中不会传递任何事件.


文档中,他们使用QThread(可能使用事件循环和工作对象)并使用QtConcurrent::run两个独立的多线程技术.它们并不意味着混合在一起.因此,线程池中没有工作对象,这只是在寻找麻烦.

问题:Qt是自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?

我认为看事情的这种方式后,答案是显而易见的是Qt的确实移动QObject自动s转换任何线程.该文档已经警告过QObjectQThread没有事件循环的情况下使用a ,就是这样.

您可以随意将它们移动到您喜欢的任何线程.但请记住,moveToThread()有时可能会导致问题.例如,如果移动工作对象涉及移动QTimer:

请注意,将重置该对象的所有活动计时器.计时器首先在当前线程中停止,然后在targetThread中重新启动(具有相同的间隔).因此,在线程之间不断移动对象可以无限期地推迟计时器事件.


结论:我认为您应该考虑使用自己的QThread运行事件循环,并在QObject那里创建您的工作而不是使用QtConcurrent.这种方式比移动方式要好得多QObject,并且可以避免使用当前方法可能产生的许多错误.查看Qt中多线程技术比较表,并选择最适合您的用例的技术.仅QtConcurrent在您只想执行一次调用函数并获取其返回值时使用.如果你想与线程永久交互,你应该转而使用自己QThread的工作者QObject.