Insufficient main loop iteration speed

I have to display data, received from a USB device, every 5ms.

I use a LevelBar and a Label widget to display the received value.

Everything seems to be running perfectly fine, but after 10 minutes or so, I realize that there is buffering meaning that the data displayed is not realtime, even if I plug-out the USB, some data keeps being displayed.

I’m supposed to make it real-time. So I was wondering if there’s a way to increase the main loop iteration speed.

Any solution/advice is appreciated.

Hi,

If you need real-time, then don’t do the acquisition in the mainloop, but from a separate thread.

Just keep in mind that Gtk is not thread-safe. You can use g_idle_add from the acquisition thread to notify the mainloop about new received data then update the display.

Thank you for your response.

I actually do the data acquisition in another thread and notify the mainloop from there. Not a GTK expert but from what I understand, notifications are queueing up and even if no data is currently being received, because of the un-processed notifications in the queue, widget value keeps being updated.

hmm… do you use g_idle_add, and if yes do you return G_SOURCE_REMOVE (or FALSE) from the callback?

If yes, that sounds like a design issue on how the data are shared between the threads.

1 Like

I actually use the exact pattern described here:
https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/sec-multithread-example.html

From the worker thread, I notify the dispatcher and dispatcher handler works in the GUI thread to update the GUI.

I use a mutex for thread safety, so every data received does get logged.

Instead of a mutex and direct update, you probably want to use an asynchronous queue: the worker thread pushes data on the queue, and the UI thread pops it.

GLib has GAsyncQueue for this task; the C++ bindings should expose something similar.

1 Like

I didn’t understand how asynchronous queue increases performance. When you’re popping from the queue, isn’t it the same thing as reading the variable once you have the mutex.

While typing the above sentence I realized that the GUI thread would be waiting the worker thread to write the data to variable, and release the mutex. With an async queue, they both would keep working without stopping each other from what I understand.

Thanks a lot!

You bypass the whole tunneling through the event loop. If that does not work out for you (or any other C++ primitive), you might find this helpful, perhaps. ChimeraTK-cppext: cppext::future_queue< T, FEATURES > Class Template Reference

1 Like

Thanks a lot for the explanation!

Everything seems to be running perfectly fine, but after 10 minutes or so, I realize that there is buffering meaning that the data displayed is not realtime, even if I plug-out the USB, some data keeps being displayed.

If you have to display real-time data, you can avoid queuing data altogether. Just enable a VSync-locked animation on the widget and show the very latest data retrieved by the USB acquisition thread.

That can be done with Gtk.Widget.add_tick_callback. Note: if you have multiple widgets that should update in real time, simply add the tick callback to one of them. Then update all the widgets from your tick callback.

/* This data structure contains the latest retrieved data
 * from the USB device. It's written from the USB thread
 * and read from UI thread. Concurrent access is protected
 * by shared_data_mutex.
 */
struct {
  int foo;
  int bar;
} shared_data;
GMutex shared_data_mutex;

/* The GTK widget showing real time USB data */
GtkWidget *usb_widget;
guint usb_widget_tick_id;

/* thread: UI
 *
 * Runs at each VSync
 */
static gboolean
usb_widget_tick_callback (GtkWidget      *widget,
                          GdkFrameClock  *frame_clock,
                          gpointer        user_data)
{
  /* Update the UI with whatever you have in shared_data.
   * In this case USB widget is a simple label */

  g_mutex_lock (&shared_data_mutex);
  char *text = g_strdup_printf ("foo: %d, bar: %d",
                                shared_data.foo,
                                shared_data.bar);
  gtk_label_set_text (GTK_LABEL (usb_widget), text);
  g_ free (text);
  g_mutex_unlock (&shared_data_mutex);

  return G_SOURCE_CONTINUE;
}

/* thread: UI */
static void
usb_widget_tick_callback_start (gpointer user_data)
{
  usb_widget_tick_id = gtk_widget_add_tick_callback (usb_widget, usb_widget_tick_callback, NULL, NULL);
}

/* thread: UI */
static void
usb_widget_tick_callback_stop (gpointer user_data)
{
  gtk_widget_remove_tick_callback (usb_widget, usb_widget_tick_id);
  usb_widget_tick_id = 0;
}

/* thread: USB */
static void
usb_acquisition_starting (void)
{
  /* Start an animation on the USB widget */
  g_idle_add_once (usb_widget_tick_callback_start, NULL);
}

/* thread: USB */
static void
usb_acquisition_have_new_data (const USBData *data)
{
  /* Update latest data for UI thread */
  g_mutex_lock (&shared_data_mutex);

  shared_data.foo = data->foo;
  shared_data.bar = data->bar;

  g_mutex_unlock (&shared_data_mutex);
}

/* thread: USB */
static void
usb_acquisition_ending (void)
{
  /* Stop the animation on the USB widget */
  g_idle_add_once (usb_widget_tick_callback_stop, NULL);
}

static void
app_activate (GtkApplication *app)
{
  GtkWidget *window = gtk_application_window_new (app);

  usb_widget = gtk_label_new (NULL);
  gtk_window_set_child (GTK_WINDOW (window), usb_widget);

  gtk_window_present (GTK_WINDOW (window));

  /* start USB thread with callbacks for notification
   * when data acquisition starts, ends, new data has
   * arrived */
  start_usb_thread (...);
}

int main (int argc, char *argv[]) {
  GtkApplication *app = gtk_application_new ("com.example.GtkApplication",
                                             G_APPLICATION_FLAGS_NONE);

  g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
  return g_application_run (G_APPLICATION (app), argc, argv);
}
2 Likes

I think this is the most suitable answer in my case because I’m trying to get as real-time as possible, and my 5ms period can possibly go lower in the future.

Thank you so much for your informative post and the sample code!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.