Calling disconnect frequently causes CPU usage to gradually rise on Qt 5.9.7 in Linux
-
The code in question handles sending data updates, so the signal slot connection is established, used, and then disconnected frequently (approximately twice a second). The connection needs to connect to relevant recipients only and then be disconnected so that the next update can be received only by parts of the application to which the update is relevant. Running this way, the CPU usage will steadily grow until it reaches 100% and crashes. Using perf to profile the datastore thread shows that the overhead is largely due to the QMetaObjectPrivate::disconnectHelper and QMetaObjectPrivate::activate. looking at the source code I don't see anything which could be causing the issue. Does anyone have any insights on what might be the reason for this behavior?
-
Without seeing your code we cannot say why CPU is going through the roof.
From your description we can say that the design needs to be revisited. If you have a list of recipient objects to connect to when you have something to send then you can just call the receiving method directly (or place the data in a queue as appropriate) in simple loop rather than abusing the signal-slot mechanism.
-
Thank you for the quick response! The goal is to receive data over Ethernet on a network thread, then share that data with a variety of QObjects on different threads, so I figured signal-slot would be a good fit for this. The code in question looks something like this:
for (object in targets) { connect(this, SIGNAL(signalDataUpdated(...), dynamic_cast<QObject*>(object), SLOT(slotDataUpdated(...)); } emit signalDataUpdated(...); for (object in targets) { disconnect(dynamic_cast<QObject*>(object), SLOT(slotDataUpdated(...)); }
I could probably rewrite this as a queue, with separate routines in each thread that periodically check for data in the queue, but signal-slots seemed to greatly simplify the architecture if I can get around this CPU usage issue with the disconnectHandler. I also forgot to mention that this came after a codebase upgrade to Qt5 from Qt4, so I'm assuming some change in the signal-slot implementation exposed a flaw in what I was doing here. And if you can point me to what I'm abusing in the signal-slot functionality, that would help me wrap my mind around how to properly use signal-slots in the future.
-
Hi,
I agree with @ChrisW67, your design is flawed.
If not all objects shall receive these messages you should rather do something like MQTT where your objects subscribe to the messages they interested in and then you dispatch upon reception. Or depending on your implementation use something like MQTT or ZMQ, etc.
-
In principle that is exactly how my code is designed: A QObject "subscribes" by adding itself to a list of QObjects that need data, paired with the types of messages they are designed to respond to. A message comes in on the network, and then its type is determined and used to filter which QObjects are subscribed to it. To be clear, I'm not really asking a networking question, I'm just trying to send data from one thread to QObjects that may be on multiple other threads.
I can think of other solutions, like the queue I mentioned, or just emitting to every possible slot and doing the filtering in each slot, but what I really want to know is what specifically am I doing wrong by using signal-slots here, particularly that would cause the disconnectHandler to be so CPU intensive? For example, are signal-slots not intended to be connected and disconnected so often? Are they not intended to be attached to some QObjects and then reattached to others later? Understanding my misuse would greatly help me when deciding when to use signal-slots elsewhere as well. Thanks again for helping with this.
-
Signal-slot connections are typically created once, usually in a constructor, and persist for the life of the objects. I would say disconnecting on-the-fly is unusual, but it may happen in a UI that is dynamically adjusted (add/remove handfuls of widgets). Doing it en masse and frequently is certainly not typical.
Looking at your code:
for (object in targets) { connect(this, SIGNAL(signalDataUpdated(...), dynamic_cast<QObject*>(object), SLOT(slotDataUpdated(...)); // in order to connect to object it must already be a QObject subclass. // The dynamic_cast should be unnecessary, and is generally considered fairly slow } emit signalDataUpdated(...); for (object in targets) { disconnect(dynamic_cast<QObject*>(object), SLOT(slotDataUpdated(...)); // ditto }
I am not sure what gyrations Qt needs to do with disconnections cross-thread, but I expect that these are slower than direct connections.
-
Thank you for the quick response! The goal is to receive data over Ethernet on a network thread, then share that data with a variety of QObjects on different threads, so I figured signal-slot would be a good fit for this. The code in question looks something like this:
for (object in targets) { connect(this, SIGNAL(signalDataUpdated(...), dynamic_cast<QObject*>(object), SLOT(slotDataUpdated(...)); } emit signalDataUpdated(...); for (object in targets) { disconnect(dynamic_cast<QObject*>(object), SLOT(slotDataUpdated(...)); }
I could probably rewrite this as a queue, with separate routines in each thread that periodically check for data in the queue, but signal-slots seemed to greatly simplify the architecture if I can get around this CPU usage issue with the disconnectHandler. I also forgot to mention that this came after a codebase upgrade to Qt5 from Qt4, so I'm assuming some change in the signal-slot implementation exposed a flaw in what I was doing here. And if you can point me to what I'm abusing in the signal-slot functionality, that would help me wrap my mind around how to properly use signal-slots in the future.
@Vivian-Scott this is the last solution I would have chosen for this situation :D
Have you tried/considered invoking the slot directly, without the connect clutch?
for (object in targets) { QMetaObject::invokeMethod(object_cast<QObject*>(object), "slotDataUpdated"); }
or, since your slots seems to take an argument:
for (object in targets) { QMetaObject::invokeMethod(object_cast<QObject*>(object), "slotDataUpdated", Q_ARG(Type, value)); }
-
Wow, I never knew about this function. This sounds perfect, thanks! Still unsure about what causes the disconnect to be so heavy in my previous implementation, but after a quick test here it looks like invokeMethod works perfectly, and is a lot cleaner too. Thanks for the help!