我在Qt5应用程序中有2个线程:
线程 A:包含一堆 QObject 派生类对象
线程B:此线程中的 worker 具有指向 A 中对象的所有指针
线程 A 有时可能非常繁忙,线程 B 仅用于委派信号和管理其他一些内容。它从不写入任何这些对象,但我需要检查一些从 A 中的对象返回布尔值的 getter 函数。
in ThreadB:
if (objInThrA->isFinished()) { ... }
isDone() 返回一个布尔值。
如果线程 A 在函数中确实很忙,并且我在线程 B 中调用这些 isFinished 函数,那么我的线程 B 会停止,直到线程 A 完成其工作,还是会工作?
Qt信号和插槽可以在不同的线程之间使用。但是有两个规则:
-
connect()
的最后一个参数应该是Qt::QueuedConection
或Qt::BlockingQueuedConnection
(或默认为Qt::AutoConnection
,这与对象属于不同线程时Qt::QueuedConnection
相同)。QueuedConnection
意味着发射器不会等待信号处理完成,BlockingQueuedConnection
意味着它这样做。 -
QObject派生类不适合在线程之间传递。在此之前,应安全地复制它们(请参阅 QMetaType 文档)。
你永远不应该访问成员或直接调用另一个线程中的对象的函数。唯一安全的方法是通过信号/插槽机制访问它们。您可以在ThreadB
中有一个信号,并将其连接到ThreadA
中的插槽,该插槽返回值:
connect(objInThrB, SIGNAL(getFinished()), objInThrA, SLOT(isFinished()), Qt::BlockingQueuedConnection);
这样,当您在线程 B 中发出信号时,如下所示:
bool ret = getFinished();
当控件返回到线程 A 的事件循环并返回值时,将调用线程 A 中的插槽。线程 B 等待调用插槽,这是因为连接类型为 BlockingQueuedConnection
。因此,请注意不要使用这种阻塞连接来阻塞应用程序主线程。
好的,所以我自己测试了它。
在线程 B 中:
connect(this,SIGNAL(runWork()),objInThrA,SLOT(doWork()));
emit runWork();
QThread::sleep(2);
qDebug() << objInThrA->isFinished();
在线程 A 中:
qDebug() << "start A sleep";
QThread::sleep(10);
qDebug() << "end A sleep";
输出:
start A sleep
false
end A sleep
它可以工作,但是我仍然不确定我是否以这种方式使用它,它是否正确完成和定义的行为。
简短的回答是否定的。
当您按所述运行时,您将直接从不在线程中的对象调用方法(该对象位于线程 A 中,但您从线程 B 调用其方法之一)。 直接调用方法不是Qt从标准C++修改而来的,所以它不通过信号、槽或事件循环来操作,并且对你的线程模型一无所知。 这意味着,如果在线程 B 中调用该方法,它将在线程 B 中运行。
如果您不小心,从另一个线程调用对象上的方法可能会很危险,因为它会引入并发问题。 如果线程 A 在线程 B 调用 getDone() 时更新_mFinished数据成员,则它可能会获得部分写入的值。 在您的特定示例中,恰好布尔值没有"部分写入"状态,您可能会没事。
解决并发问题是通过对将在多个线程之间共享的元素的原子操作来完成的。 您可以使用保证为原子读写操作的变量(如布尔值通常一样),也可以使用锁定机制来保护这些变量。 互斥体和信号量是最常见的锁定机制,允许从多个线程锁定,以限制在读取和写入变量时对变量的访问。 这样做是为了避免在读取变量时用新值部分写入它们。
如果您正在做涉及多个线程的工作,我建议您阅读互斥体和信号量(通用多线程数据结构),因为它们对于理解线程非常重要。 此外,Qt有很好的Qt包装版本,使它们易于使用,并避免了在维护其使用时的一些简单陷阱(例如QMutexLocker)。