代码之家  ›  专栏  ›  技术社区  ›  Andy

在信号被处理之前终止发出排队信号的线程是否安全?

  •  1
  • Andy  · 技术社区  · 4 年前

    在我的代码中,我有一个在单独的 std::jthread :

    // Worker.h:
    #pragma once
    #include <QWidget>
    class Worker : public QObject {
        Q_OBJECT
    public:
        Worker();
    signals:
        void sendMessage(QString msg);
    private:
        void threadFunction();
        std::jthread m_thread;
    };
    

    以及在主线程中运行的小部件:

    // Widget.h
    #pragma once
    #include <QWidget>
    class Widget : public QWidget {
        Q_OBJECT
    public:
        Widget();
    public slots:
        void getMessage(QString msg);
    };
    

    我在sendMessage信号和getMessage槽之间创建了一个排队连接:

    #include <QApplication>
    #include <chrono>
    #include "Widget.h"
    #include "Worker.h"
    
    int main(int argc, char* argv[])
    {
        QApplication a(argc, argv);
        Widget widget;
        {
            Worker worker;
            QObject::connect(&worker, &Worker::sendMessage, &widget, &Widget::getMessage, 
                             Qt::ConnectionType::QueuedConnection);
            std::this_thread::sleep_for(std::chrono::seconds(1));
        } // worker deleted and thread terminated
        widget.show();
        return a.exec();
    }
    

    Q1:在处理信号之前终止发出信号的线程是否安全?

     // Worker.cpp:
     #include "Worker.h"
     #include <chrono>
     Worker::Worker() {
        m_thread = std::jthread(&Worker::threadFunction, this);
     }
     void Worker::threadFunction() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        emit sendMessage("finished");
     }
    

    // Widget.cpp:
    #include "Widget.h"
    #include <iostream>
    
    Widget::Widget() : QWidget() {}
    
    void Widget::getMessage(QString msg) {
        std::cerr << msg.toStdString() << "\n";
    }
    

    sendMessage的发射调用了该方法:

    // moc_Worker.cpp:
    void Worker::sendMessage(QString _t1)
    {
        void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
        QMetaObject::activate(this, &staticMetaObject, 0, _a);
    }
    

    QMetaObject::activate() 定义见 qobject.cpp . 阅读关于 how Qt signals and slots are implemented 我发现这种方法查找的是 SignalVector Worker对象。此元素是一个双链表 QObjectPrivate::Connection 物体。在我的例子中,这个列表只有一个元素,它与 getMessage 方法。

    它还呼吁

    QThreadData *td = connection->receiverThreadData.loadRelaxed();
    bool receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
    

    以确定接收方是否与发送方处于同一线程中。 这个 receiverThreadData 在建立连接时设置:

    // qobject.cpp:
    QObjectPrivate::Connection *QMetaObjectPrivate::connect(...,QObject *receiver,...) {
       ...
       QThreadData *td = receiver->threadData;
       connection->receiverThreadData.storeRelaxed(td);
       ...
    }
    

    由于我正在使用 QueuedConnection 方法:

    void QObject::queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv);
    

    它依次调用:

    void QCoreApplication::postEvent(QObject *receiver, QEvent *event);
    

    用一个 QMetaCallEvent :

    struct QMetaCallEvent {
       // the signal:
       QObject *sender; 
       uint signal_index;
         
       // the slot:
       QObject *receiver;
       ushort method_offset;
           
       // the data:
       int nargs_;
       int* types_;
       void** args_;
    };
    

    Q2:在事件处理之前删除发送方对象是否安全?

    阅读关于 QObject *QObject::sender() : 如果在信号激活的槽中调用,则返回一个指向发送信号的对象的指针;否则,它返回nullptr。该指针仅在从该对象的线程上下文调用此函数的槽执行期间有效。

    如果发送器被破坏,或者插槽与发送器的信号断开连接,则此函数返回的指针将无效。"

    无效指针=可能会崩溃?

    0 回复  |  直到 4 年前
        1
  •  1
  •   m7913d    4 年前

    在发射线程终止和/或发送对象被销毁后处理信号应该是安全的。

    排队连接旨在提供线程之间简单、无阻塞和线程安全的通信。虽然没有明确记录,但如果在另一个线程中处理所有发出的信号之前关闭一个线程是不安全的,那么这种机制将变得无用,因为发出的线程/对象不知道何时处理插槽。

    当然,

    • 您应该确保传递的对象不会被销毁(在传递(智能)指针类型的情况下)。
    • 您无法访问在另一个线程中被销毁的任何对象(即。 QObject::sender() ).