0

我正在将一些套接字从主线程移动到工作线程,并在新线程上处理,readyRead()等。我很少看到以下危险警告:close()write()

“QSocketNotifier:不能从另一个线程启用或禁用套接字通知”

通常在此警告之后执行会导致未定义的行为(崩溃/未捕获的异常/正常)。
已经检查了插座的所有信号/插槽,它们看起来是正确的。根据我的经验,如果有任何编码不规则,通常上述警告会很频繁或很快。

现在我怀疑moveToThread()它是否按预期工作!因为从QEvent文档QEvent::ThreadChange中,在此函数调用期间,a 作为当前线程上的最后一个事件引发。

对象被移动到另一个线程。这是在前一个线程中发送给该对象的最后一个事件。见11在线程亲和性改变之前,一个事件被发送到这个对象。您可以处理此事件以执行任何特殊处理。QObject::moveToThread()
QEvent::ThreadChange

在我的代码中,我没有检查此事件。相反,我假设,一旦moveToThread()完成,线程亲和力就会改变,并且对象上的新“信号/插槽”在新线程上得到保证。

问题

  1. 将套接字移动到另一个线程是否安全?
  2. moveToThread()确保该对象上的信号在此函数之后也被移动到新线程?
  3. 应该如何设计以确保 Socket 没有螺纹不规则性?

顺便说一句,在最新的 Qt 调试器中,它位于以下代码段:

// qsocketnotifier.cpp
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
    qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
    return;
}

下面是主线程的堆栈帧: 在此处输入图像描述

下面是工作线程的堆栈帧,它在日志记录时停止: 在此处输入图像描述

4

2 回答 2

2

成语不支持 Qt 套接字moveToThread()!!!

我希望 Qt 将这一事实记录为 LOUD,如上所述。我花了数周时间调试一个不适合修复的问题。套接字moveToThread()问题是如此复杂,以至于它不会始终如一地发生。QTcpServer只有在测试具有大量套接字打开/关闭场景的自定义子类之后,我才能一致地重现它。

后来我看到了这篇文章:如何在不同的线程中执行 QTcpSocket?. 这个答案明确指出,跨线程的移动不支持套接字。

以下是来自的文档QTcpServer

注意:返回的QTcpSocket对象不能从另一个线程使用。如果要使用来自另一个线程的传入连接,则需要覆盖incomingConnection().

注意:如果要将传入连接作为QTcpSocket另一个线程中的新对象处理,则必须将“socketDescriptor”传递给另一个线程并在QTcpSocket那里创建对象并使用其setSocketDescriptor()方法。

可能这也适用于QWebSocketServer


所以最好的方法是重载incomingConnection()并在另一个线程中调用它的内部。

void MyTcpServer::incomingConnection (qintptr socketDescriptor) override
{
  emit SignalToDifferentThread(socketDescriptor);
}

如果这样做,可能不需要addPendingConnection()andnextPendingConnection()成语,即使在注释中提到它也是如此。

注意:如果在该方法的重新实现中创建了另一个套接字,则需要通过调用将其添加到 Pending Connections 机制中addPendingConnection()(<-- 这可能不适用于不同线程中的套接字)


QWebSocketServerQTcpServer

这是另一个事实,它浪费了我更多的时间。在QTcpServer中,我们可以重载incomingConnection()并将套接字描述符传递给另一个线程以创建套接字。但是,QWebSocketServer没有这样的过载。主要是因为,QSslSocket接收到的QTcpServer被升级到通过它的方法QWebSocket传递给了。QWebSocketServerhandleConnection()

所以QWebSocket必须在哪里创建QWebSocketServer. 因此,无论我们希望在哪个线程QWebSocket中创建 a,所有这些线程都需要具有 的对象QWebSocketServer。习惯上我们可以让一个QTcpServer端口监听一个端口,并且可以通过负载平衡的方式将它QTcpSocket的 s(升级为QWebSockets)传递给这些不同的线程。QWebSocketServer


给 Qt 开发人员的消息

我希望,您可以通过一个简单的编译错误来节省依赖 Qt 库的开发人员的大量时间。这将否定互联网上所有错误的文章,这些文章建议如何moveToThread()QTcpSocket. 只需介绍以下方法:

class QTcpServer / QTcpSocket / QWebSocketServer / QWebSocket : public QObject
{
  Q_OBJECT
  ...
  // Create this object in a desired thread instead of moving
  private: void moveToThread (QThread*) = delete; // hiding `QObject::moveToThread()`
};

更新:提出QTBUG-82373

于 2020-02-18T04:05:38.013 回答
0

来自docs的引用,提供了答案:

警告:这个函数不是线程安全的;当前线程必须与当前线程关联性相同。换句话说,这个函数只能将一个对象从当前线程“推”到另一个线程,它不能从任意线程“拉”一个对象到当前线程。然而,这个规则有一个例外:没有线程关联的对象可以被“拉”到当前线程。

于 2020-02-01T22:26:26.703 回答