【问题标题】:Is it safe to emit signal from another thread?从另一个线程发出信号是否安全?
【发布时间】:2019-02-12 14:31:29
【问题描述】:

从另一个线程向对象发出信号是否安全(如果插槽连接为QueuedConnection)?我找不到会提到这一点的特定文档,我找到的最多的 relevant quote 是这样的:

QObject 是可重入的。它的大部分非 GUI 子类,例如 QTimer, QTcpSocket、QUdpSocket 和 QProcess,也是可重入的,使得它 可以同时从多个线程使用这些类。 请注意,这些类旨在从以下位置创建和使用 在一个线程内; 在一个线程中创建一个对象并调用 不保证它在另一个线程中的功能可以正常工作

这表明它可能不行,这也适用于信号吗? QMetaObject::activate 里面有一个QMutexLocker,所以在我看来它可能是线程安全的......?

#include <QCoreApplication>
#include <QTimer>
#include <thread>
#include <iostream>

struct Foo : public QObject
{
    Q_OBJECT
public:
    Foo(QObject* parent) : QObject(parent) {}

public slots:
    void run()
    {
        connect(this, &Foo::signal, this, [] { std::cout << "activated"; }, Qt::QueuedConnection);

        std::thread t([this] { emit signal(); });
        if (t.joinable()) t.join();
    }

signals:
    void signal() const;
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Foo* b = new Foo(&a);
    QTimer::singleShot(0, b, &Foo::run);
    return a.exec();
}

【问题讨论】:

  • Qt 在Signals and Slots Across Threads 上的文档怎么样?
  • @AMA:它只提到了发送者和接收者生活在不同线程中的情况,但这是一个不同的问题......我问的是从线程中调用对象没有的信号'住在里面。
  • @Jaa-c 对象所在的位置并不重要。从另一个线程发出信号是安全的。

标签: c++ multithreading qt thread-safety qt-signals


【解决方案1】:

Qt 基于事件队列。每个 Qt 线程都有自己的队列和相关的事件循环。因此,当您遇到 2 个不同的对象存在于 2 个不同的线程并且一个通过信号/插槽机制(通过自动或队列连接)连接到另一个的情况时,在发射期间会发生以下情况:信号内的代码创建一个事件并将其发布到对象接收器的队列中。接收者的事件循环在队列中运行,找到发布的事件并执行适当的槽。

队列保证是线程安全的,因此跨线程发出信号是绝对安全的。您问题中的引述是关于您从T2T1 中的对象进行直接调用的情况。

有一篇关于线程、qobjects、信号、槽以及一切如何相互关联的精彩文章:Threads Events QObjects。如果您想更深入地了解它,我建议您阅读它。


关于有问题的代码。您有一个排队的连接,这意味着发送者和接收者是否生活在一个线程或不同的线程中并不重要。发送者和接收者是 2 个对象还是相同并不重要。所述例程将是相同的。如果您创建了自动连接,那么它将有一个直接呼叫,但您没有。以及来自documentation 的相关引用:

另一方面,您可以安全地从您的 QThread::run() 实现,因为信号发射是线程安全的

【讨论】:

  • 但我正在从另一个线程直接调用。在我的示例中,即为emit this-&gt;signal()。这没有回答问题。
  • @Jaa-c,不,你不是。您正在排队等候通话。
  • 插槽是从事件队列中调用的,是的。但信号本身是对函数的直接调用。
  • @Jaa-c,信号 不是 函数,不在 Qt 词汇表中。 Signal 是 C++ 函数,但就 Qt 而言,调用 C++ 信号函数意味着发出信号。这是两个不同的概念。
  • @Jaa-c 再迭代一下,signal方法是一个C++函数,由moc生成(可以查看moc生成的.cpp文件看它里面有什么)。根据文档,调用此方法(AKA 发出信号)是线程安全的。现在,如果您在线程之间有明确的直接连接,那么您还必须使插槽线程安全(但不要像这样滥用信号/插槽系统,因为它很容易搞砸)。
【解决方案2】:

是的,这是首选方式,因为 qt GUI 防止直接从另一个线程更改线程 所以你应该从线程一发出一个信号并在线程拖中连接它(接收它),并取决于信号类型执行你的一些线程代码 如果你试图直接执行代码你会得到这个错误

QObject::setParent: 无法设置父级,新父级在不同的线程中

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多