Properly threading model for high-performance UDP pipeline
-
Hi everyone! I have a pipeline for processing UDP packets. In short, there’s a source of UDP packets; I wrote a simple listener (producer) using the boost::asio library, then I used that rigtorp spsc code for an SPSC queue and wrote a handler (consumer) for the UDP packets.
My task now is to build a GUI wrapper around everything I’ve written. I’d like to point out that, in this case, I want to use Qt specifically as a GUI layer. I know that the Qt ecosystem is very robust and even allows you to use its own sockets, but in this case, I’ve decided to go with this particular approach.
I want to solve two problems, and I’d like to ask for your advice on how to solve them correctly!- I would like the part responsible for the GUI to be able to see the status of my UDP pipeline. That is, if an error occurs in the UDP socket, the GUI user should be able to see it. I’d also like the GUI thread to be able to monitor the connection (connect, disconnect, errors). Am I correct in thinking that callbacks would be the best solution in this case? Also, I don’t fully understand how to properly handle application threads. I plan to run a separate thread for the UDP pipeline, obviously to avoid overloading the main window.
Should I do it something like this?
// gui thread std::thread qtThread([argc, argv] { QCoreApplication app(argc, const_cast<char**>(argv)); QTimer::singleShot(1000, [] { QCoreApplication::quit(); }); app.exec(); }); qtThread.join(); // high performance worket part thread std::thread workerThread([argc, argv] { // some init here }); workerThread.join(); // now they need interract to each other, how to do it properly?- The second problem I want to solve is how to properly handle widgets in this kind of setup. I can’t afford to update the widgets every time a new package arrives. That would simply ruin the UI. Let’s say I have a statistics:
struct Stat { uint64_t dropped_packets; uint64_t receive_packets; uint64_t packets_with_err; };This part of code lives in udp part of programm. Now I want to create a widget based on this data. Here's roughly what I'm doing:
class StatModel : public QAbstractTableModel { public: explicit StatModel(Stat s, QObject* parent = nullptr) : QAbstractTableModel(parent), stat_(s) {} int rowCount(const QModelIndex& = {}) const override { return 3; } // for simplicity leave like this int columnCount(const QModelIndex& = {}) const override { return 2; } QVariant data(const QModelIndex& idx, int role) const override { if (!idx.isValid() || role != Qt::DisplayRole) return {}; static const std::array<const char*, 3> names{ "dropped_packets", "receive_packets", "packets_with_err"}; const std::array<uint64_t, 3> values{ stat_.dropped_packets, stat_.receive_packets, stat_.packets_with_err}; if (idx.column() == 0) return QString::fromLatin1(names[idx.row()]); if (idx.column() == 1) return QString::number(values[idx.row()]); return {}; } QVariant headerData(int section, Qt::Orientation o, int role) const override { if (role != Qt::DisplayRole || o != Qt::Horizontal) return {}; return section == 0 ? QStringLiteral("Field") : QStringLiteral("Value"); } void setStat(const Stat& s) { // key point stat_ = s; emit dataChanged(index(0, 1), index(2, 1), {Qt::DisplayRole}); } private: Stat stat_; // key point };If I update this every time, it will cause a performance hit because it triggers a widget redraw each time. But I can send this structure no more than a certain number of times per second. Is it even a good idea to keep an instance of the structure inside the model?
- I would like the part responsible for the GUI to be able to see the status of my UDP pipeline. That is, if an error occurs in the UDP socket, the GUI user should be able to see it. I’d also like the GUI thread to be able to monitor the connection (connect, disconnect, errors). Am I correct in thinking that callbacks would be the best solution in this case? Also, I don’t fully understand how to properly handle application threads. I plan to run a separate thread for the UDP pipeline, obviously to avoid overloading the main window.
-
Hi everyone! I have a pipeline for processing UDP packets. In short, there’s a source of UDP packets; I wrote a simple listener (producer) using the boost::asio library, then I used that rigtorp spsc code for an SPSC queue and wrote a handler (consumer) for the UDP packets.
My task now is to build a GUI wrapper around everything I’ve written. I’d like to point out that, in this case, I want to use Qt specifically as a GUI layer. I know that the Qt ecosystem is very robust and even allows you to use its own sockets, but in this case, I’ve decided to go with this particular approach.
I want to solve two problems, and I’d like to ask for your advice on how to solve them correctly!- I would like the part responsible for the GUI to be able to see the status of my UDP pipeline. That is, if an error occurs in the UDP socket, the GUI user should be able to see it. I’d also like the GUI thread to be able to monitor the connection (connect, disconnect, errors). Am I correct in thinking that callbacks would be the best solution in this case? Also, I don’t fully understand how to properly handle application threads. I plan to run a separate thread for the UDP pipeline, obviously to avoid overloading the main window.
Should I do it something like this?
// gui thread std::thread qtThread([argc, argv] { QCoreApplication app(argc, const_cast<char**>(argv)); QTimer::singleShot(1000, [] { QCoreApplication::quit(); }); app.exec(); }); qtThread.join(); // high performance worket part thread std::thread workerThread([argc, argv] { // some init here }); workerThread.join(); // now they need interract to each other, how to do it properly?- The second problem I want to solve is how to properly handle widgets in this kind of setup. I can’t afford to update the widgets every time a new package arrives. That would simply ruin the UI. Let’s say I have a statistics:
struct Stat { uint64_t dropped_packets; uint64_t receive_packets; uint64_t packets_with_err; };This part of code lives in udp part of programm. Now I want to create a widget based on this data. Here's roughly what I'm doing:
class StatModel : public QAbstractTableModel { public: explicit StatModel(Stat s, QObject* parent = nullptr) : QAbstractTableModel(parent), stat_(s) {} int rowCount(const QModelIndex& = {}) const override { return 3; } // for simplicity leave like this int columnCount(const QModelIndex& = {}) const override { return 2; } QVariant data(const QModelIndex& idx, int role) const override { if (!idx.isValid() || role != Qt::DisplayRole) return {}; static const std::array<const char*, 3> names{ "dropped_packets", "receive_packets", "packets_with_err"}; const std::array<uint64_t, 3> values{ stat_.dropped_packets, stat_.receive_packets, stat_.packets_with_err}; if (idx.column() == 0) return QString::fromLatin1(names[idx.row()]); if (idx.column() == 1) return QString::number(values[idx.row()]); return {}; } QVariant headerData(int section, Qt::Orientation o, int role) const override { if (role != Qt::DisplayRole || o != Qt::Horizontal) return {}; return section == 0 ? QStringLiteral("Field") : QStringLiteral("Value"); } void setStat(const Stat& s) { // key point stat_ = s; emit dataChanged(index(0, 1), index(2, 1), {Qt::DisplayRole}); } private: Stat stat_; // key point };If I update this every time, it will cause a performance hit because it triggers a widget redraw each time. But I can send this structure no more than a certain number of times per second. Is it even a good idea to keep an instance of the structure inside the model?
@marlonbuilder said in Properly threading model for high-performance UDP pipeline:
Should I do it something like this?
Why do you put Qt event loop in an extra thread? Just keep it in the main thread.
- I would like the part responsible for the GUI to be able to see the status of my UDP pipeline. That is, if an error occurs in the UDP socket, the GUI user should be able to see it. I’d also like the GUI thread to be able to monitor the connection (connect, disconnect, errors). Am I correct in thinking that callbacks would be the best solution in this case? Also, I don’t fully understand how to properly handle application threads. I plan to run a separate thread for the UDP pipeline, obviously to avoid overloading the main window.
-
Hi everyone! I have a pipeline for processing UDP packets. In short, there’s a source of UDP packets; I wrote a simple listener (producer) using the boost::asio library, then I used that rigtorp spsc code for an SPSC queue and wrote a handler (consumer) for the UDP packets.
My task now is to build a GUI wrapper around everything I’ve written. I’d like to point out that, in this case, I want to use Qt specifically as a GUI layer. I know that the Qt ecosystem is very robust and even allows you to use its own sockets, but in this case, I’ve decided to go with this particular approach.
I want to solve two problems, and I’d like to ask for your advice on how to solve them correctly!- I would like the part responsible for the GUI to be able to see the status of my UDP pipeline. That is, if an error occurs in the UDP socket, the GUI user should be able to see it. I’d also like the GUI thread to be able to monitor the connection (connect, disconnect, errors). Am I correct in thinking that callbacks would be the best solution in this case? Also, I don’t fully understand how to properly handle application threads. I plan to run a separate thread for the UDP pipeline, obviously to avoid overloading the main window.
Should I do it something like this?
// gui thread std::thread qtThread([argc, argv] { QCoreApplication app(argc, const_cast<char**>(argv)); QTimer::singleShot(1000, [] { QCoreApplication::quit(); }); app.exec(); }); qtThread.join(); // high performance worket part thread std::thread workerThread([argc, argv] { // some init here }); workerThread.join(); // now they need interract to each other, how to do it properly?- The second problem I want to solve is how to properly handle widgets in this kind of setup. I can’t afford to update the widgets every time a new package arrives. That would simply ruin the UI. Let’s say I have a statistics:
struct Stat { uint64_t dropped_packets; uint64_t receive_packets; uint64_t packets_with_err; };This part of code lives in udp part of programm. Now I want to create a widget based on this data. Here's roughly what I'm doing:
class StatModel : public QAbstractTableModel { public: explicit StatModel(Stat s, QObject* parent = nullptr) : QAbstractTableModel(parent), stat_(s) {} int rowCount(const QModelIndex& = {}) const override { return 3; } // for simplicity leave like this int columnCount(const QModelIndex& = {}) const override { return 2; } QVariant data(const QModelIndex& idx, int role) const override { if (!idx.isValid() || role != Qt::DisplayRole) return {}; static const std::array<const char*, 3> names{ "dropped_packets", "receive_packets", "packets_with_err"}; const std::array<uint64_t, 3> values{ stat_.dropped_packets, stat_.receive_packets, stat_.packets_with_err}; if (idx.column() == 0) return QString::fromLatin1(names[idx.row()]); if (idx.column() == 1) return QString::number(values[idx.row()]); return {}; } QVariant headerData(int section, Qt::Orientation o, int role) const override { if (role != Qt::DisplayRole || o != Qt::Horizontal) return {}; return section == 0 ? QStringLiteral("Field") : QStringLiteral("Value"); } void setStat(const Stat& s) { // key point stat_ = s; emit dataChanged(index(0, 1), index(2, 1), {Qt::DisplayRole}); } private: Stat stat_; // key point };If I update this every time, it will cause a performance hit because it triggers a widget redraw each time. But I can send this structure no more than a certain number of times per second. Is it even a good idea to keep an instance of the structure inside the model?
@marlonbuilder
For your choice of using a thread --- and especially if you are not familiar with threads and their requirements --- as @jsulm has said it is not clear why you need one at all. Try it from the main event loop and only look into threads if there is a problem. Newcomers to Qt often rush into sub-threads when there is no need.For your updating of widget(s). Assuming you have some widget listening for
dataChanged()signal. If you say that is happening too frequently and thereby causing lag --- is it? have you actually tried? if you are only emittingdataChanged()"a certain number of times per second" that may be fine, depending on how many times --- then you have a couple of choices. Basically you would want to "delay" emission of signal causing update so that you can "accumulate" multiple updates into one update for redraw. You might interpose a QIdentityProxyModel between your model and the widget to control signal emission. Or since you seem to have asetStat()which callsdataChanged()explicitly itself you might do it there. You would introduce aQTimer--- single shot or repeating --- which you start whensetStat()is called and only when that times out would you emit adataChanged(), with the data as it is at that point, having accumulated/ignored the earlier updates. Make the frequency of the timer sufficient for user, e.g. does user really need to see updates more than, say, 10 times per second?Although the situation is rather different, the principle is similar to that recently discussed in my post at https://forum.qt.io/topic/164540/spin-box-and-slider-tracking/11.
- I would like the part responsible for the GUI to be able to see the status of my UDP pipeline. That is, if an error occurs in the UDP socket, the GUI user should be able to see it. I’d also like the GUI thread to be able to monitor the connection (connect, disconnect, errors). Am I correct in thinking that callbacks would be the best solution in this case? Also, I don’t fully understand how to properly handle application threads. I plan to run a separate thread for the UDP pipeline, obviously to avoid overloading the main window.