为什么我在移动到另一个线程时出现运行时错误,使用套接字 (TCP/Web)? `QObject::moveToThread()` 是同步调用吗?
Why am I getting runtime errors, with Sockets (TCP/Web) when moved to another thread? Is `QObject::moveToThread()` a synchronous call?
我正在将一些套接字从主线程移动到辅助线程,并在新线程上处理 readyRead()
、close()
、write()
等。我很少看到以下危险警告:
"QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"
通常此警告后的执行会导致未定义的行为(崩溃/未捕获的异常/正常)。
已检查所有 signal/slot 到套接字,它们似乎正确。根据我的经验,如果有任何编码异常,通常会频繁或快速地出现上述警告。
现在我怀疑 moveToThread()
是否按预期工作!因为根据 QEvent 文档,在此函数调用期间,QEvent::ThreadChange
作为当前线程上的最后一个事件引发。
The object is moved to another thread. This is the last event sent to this object in the previous thread. See QObject::moveToThread()
1.
1 A QEvent::ThreadChange
event is sent to this object just before the thread affinity is changed. You can handle this event to perform any special processing.
在我的代码中,我没有检查这个事件。相反,我假设,一旦 moveToThread()
完成,线程关联就会改变,并且对象上的新 "signal/slot" 在新线程上得到保证。
问题:
- 将套接字移动到另一个线程是否安全?
moveToThread()
能否确保该对象上的信号也被移动到紧接此函数之后的新线程?
- 应该如何设计以确保套接字没有线程不规则?
顺便说一句,在最新的 Qt 调试器中,它位于以下代码段:
// qsocketnotifier.cpp
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
return;
}
下面是主线程的栈帧:
下面是工作线程的堆栈帧,它在日志记录时停止:
来自 docs 的引述提供了答案:
Warning: This function is not thread-safe; the current thread must be
same as the current thread affinity. In other words, this function can
only "push" an object from the current thread to another thread, it
cannot "pull" an object from any arbitrary thread to the current
thread. There is one exception to this rule however: objects with no
thread affinity can be "pulled" to the current thread.
Qt 套接字不支持 moveToThread()
习语!!!
我希望 Qt 像上面那样把这个事实记录下来。我花了数周时间调试一个不适合修复的问题。套接字 moveToThread()
问题非常复杂,不会一直发生。只有在使用大量套接字 open/close 场景测试自定义 QTcpServer
子类后,我才能始终如一地重现它。
后来我遇到这个 post:How do I execute QTcpSocket in a different thread?. This answer 明确指出套接字不支持跨线程移动。
这是来自 QTcpServer
的文档
Note: The returned QTcpSocket
object cannot be used from another thread. If you want to use an incoming connection from another thread, you need to override incomingConnection()
.
Note: If you want to handle an incoming connection as a new QTcpSocket
object in another thread you have to pass the "socketDescriptor" to the other thread and create the QTcpSocket
object there and use its setSocketDescriptor()
method.
可能这也适用于 QWebSocketServer
!
所以最好的方法是重载incomingConnection()
并在另一个线程中调用它的内部。
void MyTcpServer::incomingConnection (qintptr socketDescriptor) override
{
emit SignalToDifferentThread(socketDescriptor);
}
如果这样做,可能不需要 addPendingConnection()
和 nextPendingConnection()
习语,即使在注释中提到了它。
Note: If another socket is created in the reimplementation of this method, it needs to be added to the Pending Connections mechanism by calling addPendingConnection()
. (<-- this may not apply for the sockets in different thread)
QWebSocketServer
比 QTcpServer
更严格
这是另一个事实,它浪费了我更多的时间。在 QTcpServer
中,我们可以重载 incomingConnection()
并将套接字描述符传递给另一个线程以创建套接字。但是,QWebSocketServer
没有这样的重载。主要是因为 QTcpServer
中收到的 QSslSocket
升级到 QWebSocket
并通过其 handleConnection()
方法传递给 QWebSocketServer
。
因此必须在 QWebSocketServer
所在的位置创建 QWebSocket
!。因此,无论在哪个线程中,我们都希望创建一个 QWebSocket
,所有这些线程都需要有一个 QWebSocketServer
的对象。惯用地,我们可以让一个 QTcpServer
侦听单个端口,并可以在负载中继续将其 QTcpSocket
s(升级到 QWebSocket
s)传递给这些不同线程的 QWebSocketServer
s平衡方式。
致 Qt 开发人员的一封信
我希望,您可以通过一个简单的编译错误为依赖 Qt 库的开发人员节省大量时间。这将否定所有流传在互联网上的错误文章,这些文章建议如何在 QTcpSocket
上使用 moveToThread()
。简单介绍一下方法:
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。
我正在将一些套接字从主线程移动到辅助线程,并在新线程上处理 readyRead()
、close()
、write()
等。我很少看到以下危险警告:
"QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"
通常此警告后的执行会导致未定义的行为(崩溃/未捕获的异常/正常)。
已检查所有 signal/slot 到套接字,它们似乎正确。根据我的经验,如果有任何编码异常,通常会频繁或快速地出现上述警告。
现在我怀疑 moveToThread()
是否按预期工作!因为根据 QEvent 文档,在此函数调用期间,QEvent::ThreadChange
作为当前线程上的最后一个事件引发。
The object is moved to another thread. This is the last event sent to this object in the previous thread. See
QObject::moveToThread()
1.
1 AQEvent::ThreadChange
event is sent to this object just before the thread affinity is changed. You can handle this event to perform any special processing.
在我的代码中,我没有检查这个事件。相反,我假设,一旦 moveToThread()
完成,线程关联就会改变,并且对象上的新 "signal/slot" 在新线程上得到保证。
问题:
- 将套接字移动到另一个线程是否安全?
moveToThread()
能否确保该对象上的信号也被移动到紧接此函数之后的新线程?- 应该如何设计以确保套接字没有线程不规则?
顺便说一句,在最新的 Qt 调试器中,它位于以下代码段:
// qsocketnotifier.cpp
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
return;
}
下面是主线程的栈帧:
下面是工作线程的堆栈帧,它在日志记录时停止:
来自 docs 的引述提供了答案:
Warning: This function is not thread-safe; the current thread must be same as the current thread affinity. In other words, this function can only "push" an object from the current thread to another thread, it cannot "pull" an object from any arbitrary thread to the current thread. There is one exception to this rule however: objects with no thread affinity can be "pulled" to the current thread.
Qt 套接字不支持 moveToThread()
习语!!!
我希望 Qt 像上面那样把这个事实记录下来。我花了数周时间调试一个不适合修复的问题。套接字 moveToThread()
问题非常复杂,不会一直发生。只有在使用大量套接字 open/close 场景测试自定义 QTcpServer
子类后,我才能始终如一地重现它。
后来我遇到这个 post:How do I execute QTcpSocket in a different thread?. This answer 明确指出套接字不支持跨线程移动。
这是来自 QTcpServer
Note: The returned
QTcpSocket
object cannot be used from another thread. If you want to use an incoming connection from another thread, you need to overrideincomingConnection()
.Note: If you want to handle an incoming connection as a new
QTcpSocket
object in another thread you have to pass the "socketDescriptor" to the other thread and create theQTcpSocket
object there and use itssetSocketDescriptor()
method.
可能这也适用于 QWebSocketServer
!
所以最好的方法是重载incomingConnection()
并在另一个线程中调用它的内部。
void MyTcpServer::incomingConnection (qintptr socketDescriptor) override
{
emit SignalToDifferentThread(socketDescriptor);
}
如果这样做,可能不需要 addPendingConnection()
和 nextPendingConnection()
习语,即使在注释中提到了它。
Note: If another socket is created in the reimplementation of this method, it needs to be added to the Pending Connections mechanism by calling
addPendingConnection()
. (<-- this may not apply for the sockets in different thread)
QWebSocketServer
比 QTcpServer
更严格
这是另一个事实,它浪费了我更多的时间。在 QTcpServer
中,我们可以重载 incomingConnection()
并将套接字描述符传递给另一个线程以创建套接字。但是,QWebSocketServer
没有这样的重载。主要是因为 QTcpServer
中收到的 QSslSocket
升级到 QWebSocket
并通过其 handleConnection()
方法传递给 QWebSocketServer
。
因此必须在 QWebSocketServer
所在的位置创建 QWebSocket
!。因此,无论在哪个线程中,我们都希望创建一个 QWebSocket
,所有这些线程都需要有一个 QWebSocketServer
的对象。惯用地,我们可以让一个 QTcpServer
侦听单个端口,并可以在负载中继续将其 QTcpSocket
s(升级到 QWebSocket
s)传递给这些不同线程的 QWebSocketServer
s平衡方式。
致 Qt 开发人员的一封信
我希望,您可以通过一个简单的编译错误为依赖 Qt 库的开发人员节省大量时间。这将否定所有流传在互联网上的错误文章,这些文章建议如何在 QTcpSocket
上使用 moveToThread()
。简单介绍一下方法:
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。