问题
I am moving some sockets from main thread to worker thread and processing readyRead()
, close()
, write()
etc. on the new thread. Rarely I see below dangerous warning:
"QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"
Usually the execution after this warning results in undefined behaviour (crash / uncaught exception / normal).
Have checked all the signal/slot to the socket and they seem proper. From my experience, usually the above warning will be frequent or quick if there is any coding irregularity.
Now I am suspecting if moveToThread()
does its job as expected or not! Because from QEvent documentation a QEvent::ThreadChange
is raised as the last event on the current thread during this function call.
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.
In my code, I am not checking for this event. Rather I assume that, once the moveToThread()
is finished, the thread affinity is changed and the new "signal/slot" on the object is guaranteed on the new thread.
Question:
- Is it safe to move the sockets to another thread?
- Will
moveToThread()
assure that the signals on that object are also moved to the new thread just after this function? - How should it be designed to assure no threading irregularity with Sockets?
BTW, in the latest Qt debugger it lands on following code segment:
// qsocketnotifier.cpp
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
return;
}
Below is the stack frame for the main thread:
Below is the stack frame for the worker thread, which halts on the logging:
回答1:
Qt sockets are NOT supported with moveToThread()
idiom!!!
I wish Qt had documented this fact as LOUD as above. I have spent weeks of debugging an issue which is not meant for fixing. Socket moveToThread()
issue is so complicated, that it doesn't happen consistently. I could reproduce it consistently only after testing a customised QTcpServer
subclass with lots of socket open/close scenario.
Later I came across this post: How do I execute QTcpSocket in a different thread?. This answer explicitly states that sockets are not supported for the movement across the threads.
Here are the documentations from 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.
Probably this will apply to the QWebSocketServer
too!
So the best approach is to overload incomingConnection()
and call its internal in another thread.
void MyTcpServer::incomingConnection (qintptr socketDescriptor) override
{
emit SignalToDifferentThread(socketDescriptor);
}
If done so, the addPendingConnection()
and nextPendingConnection()
idiom may not be required, even though it's mentioned in a note.
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
is stricter than QTcpServer
This is another fact, which wasted more time for me. In QTcpServer
, we can overload the incomingConnection()
and pass on the socket descriptor to another thread to create a socket. However, QWebSocketServer
doesn't have such overload. Mostly because, QSslSocket
received in QTcpServer
gets upgraded to QWebSocket
and passed on to the QWebSocketServer
via its handleConnection()
method.
So QWebSocket
must be created where QWebSocketServer
resides!. Hence in whichever threads, we want a QWebSocket
to be created, all those threads need to have an object of QWebSocketServer
. Idiomatically we can have one QTcpServer
listening on a single port and can keep passing its QTcpSocket
s (upgraded to QWebSocket
s) to these various threads' QWebSocketServer
s in a load balancing way.
A message to Qt developers
I wish that, you can save lot of time of the developers relying on the Qt library by a simple compilation error. This will negate all the wrong articles floating around internet which suggest how to use moveToThread()
on a QTcpSocket
. Just introduce below method:
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()`
};
Update: Raised QTBUG-82373.
回答2:
A quote from the docs, that provides the answer:
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.
来源:https://stackoverflow.com/questions/60019664/why-am-i-getting-runtime-errors-with-sockets-tcp-web-when-moved-to-another-th