GTK C development; question/concern wrt gtk_dialog_run


 
Thread Tools Search this Thread
Top Forums Programming GTK C development; question/concern wrt gtk_dialog_run
# 1  
Old 10-13-2011
GTK C development; question/concern wrt gtk_dialog_run

I'm new to GTK development, so I've been going through the examples just to capture the basics. I've done a lot of Unix GUI development before, but it was all Xt/Motif stuff. So, the GTK scheduler is something new to me.

That said, I was going through the color selection example here, and was pretty curious how it handled the gtk_dialog_run command. I was mostly interested if during the call (while it was waiting for a response on line 132) a timer would continue to run. I though, for sure, it wouldn't.

To my surprise, a timer (added via g_timeout_add) did continue on in the background. My first thought was, "oh, neat, they threaded the timers." But then I figured, "but, why not see." So I threw together the code below:

Code:
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>

GtkWidget *colorseldlg = NULL;
GtkWidget *drawingarea = NULL;
GdkColor color;

static pthread_key_t _g_uidPTK;

typedef struct
{
	int		tid;
	
} tspecific_t;

typedef struct
{
	int		 val;
	char	*txt;

} app_timer_t;

static tspecific_t *get_tsd()
{
	static pthread_mutex_t	 mx = PTHREAD_MUTEX_INITIALIZER;
	static int				 nextId = 1;
	
	tspecific_t				*tdata;
	
	if ((tdata = pthread_getspecific(_g_uidPTK)) == NULL)
	{
		tdata = malloc(sizeof(*tdata));
	
		pthread_setspecific(_g_uidPTK, tdata);
		
		pthread_mutex_lock(&mx);
		tdata->tid = nextId;
		nextId++;
		pthread_mutex_unlock(&mx);	
	}
	
	return tdata;
}

static int get_tid()
{
	tspecific_t				*tdata = get_tsd();
	
	return tdata->tid;
}

static gboolean progress_timeout( gpointer _timer )
{
  #define timer	((app_timer_t *) _timer)
	
  printf("[tid:%d] called %s [%d]...\n", get_tid(), timer->txt, ++(timer->val));
  
  /* As this is a timeout function, return TRUE so that it
   * continues to get called */
  return TRUE;
  
  #undef timer
} 

/* Color changed handler */

static void color_changed_cb( GtkWidget         *widget,
                              GtkColorSelection *colorsel )
{
  GdkColor ncolor;

  printf("[tid:%d] color changed...\n", get_tid());
  
  gtk_color_selection_get_current_color (colorsel, &ncolor);
  gtk_widget_modify_bg (drawingarea, GTK_STATE_NORMAL, &ncolor);       
}

/* Drawingarea event handler */

static gboolean area_event( GtkWidget *widget,
                            GdkEvent  *event,
                            gpointer   client_data )
{
  gint handled = FALSE;
  gint response;
  GtkColorSelection *colorsel;

  /* Check if we've received a button pressed event */
  
  printf("[tid:%d] Got event %d...\n", get_tid(), event->type);
  
  if (event->type == GDK_BUTTON_PRESS)
    {
      handled = TRUE;

       /* Create color selection dialog */
      if (colorseldlg == NULL)
        colorseldlg = gtk_color_selection_dialog_new ("Select background color");

      /* Get the ColorSelection widget */
      colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (colorseldlg)->colorsel);

      gtk_color_selection_set_previous_color (colorsel, &color);
      gtk_color_selection_set_current_color (colorsel, &color);
      gtk_color_selection_set_has_palette (colorsel, TRUE);

      /* Connect to the "color_changed" signal, set the client-data
       * to the colorsel widget */
      g_signal_connect (colorsel, "color_changed",
                        G_CALLBACK (color_changed_cb), (gpointer) colorsel);

      /* Show the dialog */
      printf("[tid:%d] Running dialog...\n", get_tid());
      
      response = gtk_dialog_run (GTK_DIALOG (colorseldlg));

      printf("[tid:%d] Got response.\n", get_tid());
      
      if (response == GTK_RESPONSE_OK)
        gtk_color_selection_get_current_color (colorsel, &color);
      else 
        gtk_widget_modify_bg (drawingarea, GTK_STATE_NORMAL, &color);

      gtk_widget_hide (colorseldlg);
    }

  return handled;
}

/* Close down and exit handler */

static gboolean destroy_window( GtkWidget *widget,
                                GdkEvent  *event,
                                gpointer   client_data )
{
  gtk_main_quit ();
  return TRUE;
}

/* Main */

gint main( gint   argc,
           gchar *argv[] )
{
  GtkWidget		*window;
  app_timer_t	 t1 = { 0, "T1" };
  app_timer_t	 t2 = { 0, "T2" };

  /* Initialize the toolkit, remove gtk-related commandline stuff */

  gtk_init (&argc, &argv);
  
  /* create key for thread assignment */
  
  pthread_key_create(&_g_uidPTK, free);

  /* Create toplevel window, set title and policies */

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Color selection test");
  gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);

  /* Attach to the "delete" and "destroy" events so we can exit */

  g_signal_connect (window, "delete-event",
                    G_CALLBACK (destroy_window), (gpointer) window);
  
  /* Create drawingarea, set size and catch button events */

  drawingarea = gtk_drawing_area_new ();

  color.red = 0;
  color.blue = 65535;
  color.green = 0;
  gtk_widget_modify_bg (drawingarea, GTK_STATE_NORMAL, &color);       

  gtk_widget_set_size_request (GTK_WIDGET (drawingarea), 200, 200);

  gtk_widget_set_events (drawingarea, GDK_BUTTON_PRESS_MASK);

  g_signal_connect (GTK_OBJECT (drawingarea), "event", 
	            GTK_SIGNAL_FUNC (area_event), (gpointer) drawingarea);
  
  /* Add drawingarea to window, then show them both */

  gtk_container_add (GTK_CONTAINER (window), drawingarea);

  gtk_widget_show (drawingarea);
  gtk_widget_show (window);
  
  /* add a few timeouts to test how they are handled */
  
  g_timeout_add (2000, progress_timeout, &t1);
  g_timeout_add (3000, progress_timeout, &t2);
  
  /* Enter the gtk main loop (this never returns) */
    
  gtk_main ();

  /* Satisfy grumpy compilers */

  return 0;
}

Most of it is their code, I just added the stuff to assign and retrieve a unique ID for each thread.

Upon running it, I was further surprised! The timers and the call waiting for the dialog to come back are "running" in the same thread. Here's some output:

Code:
[tid:1] Got event 13...
[tid:1] Got event 2...
[tid:1] Got event 2...
[tid:1] called T1 [1]...
[tid:1] called T2 [1]...
[tid:1] Got event 4...
[tid:1] Running dialog...
[tid:1] called T1 [2]...
[tid:1] called T1 [3]...
[tid:1] called T2 [2]...
[tid:1] called T1 [4]...
[tid:1] called T2 [3]...
[tid:1] Got response.
[tid:1] called T1 [5]...
[tid:1] called T1 [6]...
[tid:1] called T2 [4]...

Humm...now, I know this can be accomplished with some funky long jumping and the like. A quick debugging session with GDB further proves that setting a breakpoint inside the timer and inspecting the stack have them both "running" together. See below:

Code:
(gdb) break 61
Breakpoint 1 at 0x80490b5: file gtktst.c, line 61.
(gdb) run
Starting program: /home/dreamwarrior/CStuff/gtkStuff/gtktst 
[Thread debugging using libthread_db enabled]
[tid:1] Got event 13...
[tid:1] Got event 2...
[tid:1] Got event 2...
[tid:1] Got event 4...
[tid:1] Running dialog...

Breakpoint 1, progress_timeout (_timer=0xbffff394) at gtktst.c:61
61	  printf("[tid:%d] called %s [%d]...\n", get_tid(), timer->txt, ++(timer->val));
(gdb) where
#0  progress_timeout (_timer=0xbffff394) at gtktst.c:61
#1  0x008b7d5c in ?? () from /lib/libglib-2.0.so.0
#2  0x008b75e5 in g_main_context_dispatch () from /lib/libglib-2.0.so.0
#3  0x008bb2d8 in ?? () from /lib/libglib-2.0.so.0
#4  0x008bb817 in g_main_loop_run () from /lib/libglib-2.0.so.0
#5  0x001deabf in gtk_dialog_run () from /usr/lib/libgtk-x11-2.0.so.0
#6  0x0804926d in area_event (widget=0x8077858, event=0x80774b0, 
    client_data=0x8077858) at gtktst.c:120
#7  0x0026b424 in ?? () from /usr/lib/libgtk-x11-2.0.so.0
#8  0x00834252 in g_closure_invoke () from /usr/lib/libgobject-2.0.so.0
#9  0x0084899d in ?? () from /usr/lib/libgobject-2.0.so.0
#10 0x00849c33 in g_signal_emit_valist () from /usr/lib/libgobject-2.0.so.0
#11 0x0084a256 in g_signal_emit () from /usr/lib/libgobject-2.0.so.0
#12 0x003984da in ?? () from /usr/lib/libgtk-x11-2.0.so.0
#13 0x00263a5d in gtk_propagate_event () from /usr/lib/libgtk-x11-2.0.so.0
#14 0x002650c7 in gtk_main_do_event () from /usr/lib/libgtk-x11-2.0.so.0
#15 0x0055939a in ?? () from /usr/lib/libgdk-x11-2.0.so.0
#16 0x008b75e5 in g_main_context_dispatch () from /lib/libglib-2.0.so.0
#17 0x008bb2d8 in ?? () from /lib/libglib-2.0.so.0
#18 0x008bb817 in g_main_loop_run () from /lib/libglib-2.0.so.0
#19 0x002653c9 in gtk_main () from /usr/lib/libgtk-x11-2.0.so.0
#20 0x08049523 in main (argc=1, argv=0xbffff464) at gtktst.c:203
(gdb) quit

This sort of thing has me worried. What is safe to do in my timer with other code sitting on the stack? I certainly couldn't grab a mutex before the gtk_dialog_run call that any timer would grab, I'd deadlock! So...what else isn't safe? The documentation isn't really clear, and in fact, to me it implies that gtk_dialog_run "blocks" the event loop; but it obviously doesn't.
# 2  
Old 10-13-2011
I think you're overthinking this. This isn't pretend-multithreading with longjmp, this is just ordinary event-based programming. gtk has a big list of timers and things receiving events. It waits for events or timeouts and calls the necessary callbacks when they happen. Things like gtk_main_quit() just alter variables inside the GTK event loop.

You make lots of GTK calls inside area_event, too. Anything in there could be checking the timer. No longjmp() trickery necessary.

It's not multithreaded by design. Data visibility isn't a problem inside one thread, there's no race conditions and no magic things to be done to let other callbacks see your own changes.

If you're worried about things deadlocking inside GTK, you may be wanting to do things which probably shouldn't be inside the GTK loop. Keep a work thread that does no GTK stuff at all and let GTK do its own thing.
# 3  
Old 10-13-2011
Good point, I suppose they could have duplicated the event loops in the run dialog call or otherwise called out to some common dispatch functionality from within there.

Either way, all valid points. Maybe I am reading too much into it.

On the note of a work thread: is GTK generally thread un-safe like the Xt scheduler? Do I have to do some funky stuff if, for example, a work thread notices some state change it wants to relay to the GUI? In the past, I've handled this by creating a mutex protected queue having a pipe as a "signal" to an input handler that there is data (or lack thereof) on the queue that the GUI needs to read. I'd have preferred to just update the GUI within the work-thread, but that would crash an Xt based application.
# 4  
Old 10-13-2011
It's not a question of threadsafe vs thread-unsafe. mutex calls actually trigger events in the processor to synchronize data between threads. That has to happen one way or another to guarantee data from one thread gets seen in another. It's possible without in some circumstances, but only guaranteed when you mutex. They call it a memory barrier.

mutex it properly and data should be visible.
# 5  
Old 10-13-2011
Quote:
Originally Posted by Corona688
It's not a question of threadsafe vs thread-unsafe. mutex calls actually trigger events in the processor to synchronize data between threads. That has to happen one way or another to guarantee data from one thread gets seen in another. It's possible without in some circumstances, but only guaranteed when you mutex. They call it a memory barrier.

mutex it properly and data should be visible.
I know that...I suppose what I'm asking is, succinctly, if GTK mutex protects its data and is, therefore, thread safe. Xt did not and was, therefore, thread "unsafe". Not because of an issue with the processor cache not being cleared (which is why the memory barrier is needed) but because of potential data corruption.

At any rate...I assume it's probably not. And, the real reason I was concerned with the way GTK handled the stacking was because it acts similarly to how a signal acts (though a lot more predictably) in which case, most functions are not async-signal (and certainly therefore not thead) safe. Which would limit what I could safely invoke within a timer. Fortunately, you've put my mind to rest, by reminding me that this is predictable and probably just a controlled execution of the event dispatch loop from within the gtk_dialog_run function.

Still, I must admit, I was surprised that the main event loop didn't stop...though I suppose Xt would probably have similar behavior, only it would dispatch a callback through the primary event loop rather than unwind the stack back to the caller of gtk_dialog_run.

Humm...thinking of that, I wonder what'd happen if, while gtk_dialog_run was stacked, someone called gtk_main_quit. Would/could it unwind back? Hummm...not without long jump in that scenario.

edit: and, it seems you may indeed be right, gtk_main_quit is entirely ignored if gtk_dialog_run is stacked. It doesn't even bother to exit the dialog and return some "quit" value. However, when the dialog is closed, the caller of gtk_dialog_run is returned control and, upon it's unwinding off the stack, the primary main loop is reactivated and terminated. Hummm...while convenient, I think I prefer Xt's handling of dialogs. Is there something similar in GTK?
# 6  
Old 10-14-2011
Quote:
Originally Posted by DreamWarrior
I know that...I suppose what I'm asking is, succinctly, if GTK mutex protects its data and is, therefore, thread safe. Xt did not and was, therefore, thread "unsafe".
You're getting confused again. Of course you need to mutex your data, and probably keep it in one thread. Why would a single-threaded anything have IPC in it? It's up to the programmer to decide how they want to use it.
Quote:
At any rate...I assume it's probably not.
Unless it mutexed each and every last memory access, how could anything possibly know which things to protect? Again -- up to the programmer.

There's a simple answer to the problem of you calling things like that deep in event handlers; don't do things like that deep in event handlers... it's an interface, you don't need to bury your whole program in it.
# 7  
Old 10-14-2011
Quote:
Originally Posted by Corona688
You're getting confused again. Of course you need to mutex your data, and probably keep it in one thread. Why would a single-threaded anything have IPC in it? It's up to the programmer to decide how they want to use it. Unless it mutexed each and every last memory access, how could anything possibly know which things to protect? Again -- up to the programmer.

There's a simple answer to the problem of you calling things like that deep in event handlers; don't do things like that deep in event handlers... it's an interface, you don't need to bury your whole program in it.
Ugh...I think I'm not communicating my thoughts well enough or our wires are crossed. I'm certainly not confused, though, lol. I'm adept at multi-threaded programming and GUI development (and even widget development) in Xt. So, let me try again.

In Xt, if I created a separate worker thread to, for example, handle socket communication outside of the Xt scheduler, I would not be able to have that thread update any GUI elements (for example call XmListAddItem).

Simple example, an application that collects stats and displays them on a GUI. If the stats collection was performed in a separate thread, I couldn't update the stats on the GUI from within that thread because Xt would crash. Neither it nor the Motif widgets protected their data, so I'd end up corrupting it because both the event loop and stats thread would/could attempt to access the same widgets at the same time, for example a list widget could be exposing itself while the stats thread was calling XmListAddItem.

To rectify this, I'd have the stats collection thread pass, via a mutex protected queue using a pipe attached to an Xt "read-ready" input handle as a "there's data there", a message to the GUI thread, more specifically the input callback on the pipe that would be invoked from the main loop executing in that thread, that there were new stats. Since the GUI's thread, running Xt's main loop, would now render the stats there was no possibility that the main loop could receive an expose event concurrently and mess up the widgets.

Now, I'm asking if GTK would suffer the same "problem" or if it and its widgets have thought out multi-threaded access to them. My guess is, no.

Make sense....?
Login or Register to Ask a Question

Previous Thread | Next Thread
Login or Register to Ask a Question