GTK and threading - Displaying value from a thread into a widget - c

My objective is to read a large file. Each time I read a new line from the file, I want to display the current line number on the GTK UI.
I've read several excellent articles on SO and other places for doing this, none of which are giving me the desired sequencing of the threads. Below is an outline of my latest attempt. I launch a worker thread that uses g_idle_add to report the current line in the main loop. Unfortunately, the function called by g_idle_add runs only once and after the worker thread completes. Why does this happen?
/* Function that displays the counter in a GTK widget. Unfortunately, this thread runs AFTER proces_huge_file is complete. */
void display(gpointer data) {
/* Display the current line number in the GTK UI */
return FALSE;
}
/* Thread that reads the file line-by-line, and launches a function to display the current line number in the main loop. */
void process_huge_file(gpointer *data) {
gint line_number = 0;
while (getline(&csv_line, &len, fp) != -1) {
line_number ++; /* Current line number, add it to the data structure */
g_idle_add(G_SOURCE_FUNC(line_number_in_status_bar), data);
/* Process the line */
}
}
void on_app_activate(GApplication *app, gpointer data) {
/* Build the GTK UI and start running the worker thread. */
GThread *thread = g_thread_new ("process_huge_file", (GThreadFunc) process_huge_file, data);
g_thread_join (thread);
}

I resolved the matter, but I'm not sure I could defend this in a code review. Someone needs to write a complete book on GTK threading. (Huge thanks to the developers who posted a full threaded example on github.)
The strategy is to launch a worker thread that reads the file, and that thread sends a status to another thread with access to the main loop. Here are the components.
Inside main, we connect a signal to a button. Clicking the button launches the function that processes the file.
int main(int argc, char *argv[]) {
g_signal_connect(G_OBJECT(button_go), "clicked", G_CALLBACK(process_file), data_passer);
}
In the function that processes the file, we launch the worker thread.
gboolean process_file(GtkButton *button, gpointer data) {
/* Get a pointer to the main loop. */
gloop = g_main_loop_new(NULL, FALSE);
/* Launch the worker thread.*/
GThread *process_g_thread = g_thread_new("process_thread", (GThreadFunc)process_thread, gloop);
/* Continue running the main loop while the worker is running. */
g_main_loop_run(gloop);
/* Halt further processing in the main loop at this point until the worker thread completes. */
g_thread_join(process_g_thread);
/* Decrement the reference count for the main loop. */
g_main_loop_unref(gloop);
}
Inside process_thread worker, send calls to a thread with access to the main GTK loop for updating the UI.
gboolean process_thread(gpointer data) {
while (getline(&csv_line, &len, file_handle) != -1) {
/* Every time we read a line, increment the current line number. */
current_line_number++;
/* When the main loop is idle, call a function that displays the current line number in the main loop. */
gdk_threads_add_idle((GSourceFunc)line_number_in_status_bar, data);
}
}
Inside the thread with access to the main loop, display the current line number.
gboolean line_number_in_status_bar(gpointer data) {
/* Get the status bar's context, remove any current message. */
guint status_bar_context_info = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_bar), "STATUS_BAR_CONTEXT_INFO");
gtk_statusbar_remove(GTK_STATUSBAR(data_passer->status_bar), status_bar_context_info, data_passer->status_bar_context_info_message_id);
/* Construct a message to display in the status bar. */
gchar progress_message[100];
g_snprintf(progress_message, 100, "Reading line %d...", data_passer->current_line_number);
/* Push the message to the status bar. */
data_passer->status_bar_context_info_message_id = gtk_statusbar_push(GTK_STATUSBAR(data_passer->status_bar), status_bar_context_info, progress_message);
/* Return FALSE to remove the source function. */
return FALSE;
}

Related

The cancelation cleanup handler routine doesn't execute after thread cancelation

Task
I should write a program which creates a thread with printing text routine, and after two seconds of executing this thread should be canceled and the message of cancelling should be printed. The task condition is such that phtread_cleanup_push(3C) should be used for this purpose.
A little description of my code
I create a thread by pthread_create() calling with an infinity routine of text printing. The thread is created with default values of cancelation state and cancelation type, so the cancelation of the created thread occurs when the thread reaches the cancelation point. The main thread waits two seconds and then calls pthread_cancel(3C) to the child thread.
The created thread is printing the text with 0,1 second time interval using usleep(). Also I pasted pthread_testcancel() call inside infinity loop. So we have two cancelation point inside the loop: one is the system defined - usleep() and the second is created by calling pthread_testcancel().
Before the infinity loop I pasted the pthread_cleanup_push() with another printing text routine. And after the infinity loop I pasted pthread_cleanup_pop() statement, because both of this statements are implemented as the macro and according to the official documentation they should be in the same lexical scope.
Problem
Unfortunately cleanup handler routine doesn't executed. Child thread is canceling but no message about it is printing. I've been searching the answer for my problem for a long time, but nothing I've found was similar to my problem, so I'll be very thankful for any recommendation about solving my problem.
Code and environment
Here is my code below and it's output. I am using Oracle VM Virtual Box on Windows 10 (64-bit) on which Oracle Solaris-10 (64-bit) is running. I compile my program using bash 3.2 with the command:
gcc -o lab5 -threads -lpthread -std=c99 lab5.c
code:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define SUCCESS 0
#define FAILED 1
#define WAIT_TIME 2
#define PRINT_DELTA_TIME 1 * 100000 // in microseconds
char* printing_text = "Child thread text\n"; // this text will be printed by child thread
char* termination_text = "Child thread is terminated\n";
void cleanup_handler(void* arg) {
printf(termination_text);
}
// infinity routine of printing text
void *print_text() {
pthread_cleanup_push(cleanup_handler, NULL); // push cleanup routine to the stack
while (1) {
pthread_testcancel();
printf("%s\n", printing_text);
usleep(PRINT_DELTA_TIME);
}
pthread_cleanup_pop(1);
return NULL;
}
// cancel a thread with exception handling
int try_cancel_thread(pthread_t *thread) {
int code = pthread_cancel(*thread);
if (code != SUCCESS) {
char* error_message = strerror(code);
fprintf(stderr, "%s%s\n", "can't cancel a child thread:", error_message);
return FAILED;
}
return SUCCESS;
}
// creating a thread with exception handling
int try_create_thread(pthread_t *thread) {
int code = pthread_create(thread, NULL, print_text, NULL);
if (code != SUCCESS) {
char* error_message = strerror(code);
fprintf(stderr, "%s%s\n", "can't create a child thread:", error_message);
return FAILED;
}
return SUCCESS;
}
int main() {
pthread_t child_thread;
if (try_create_thread(&child_thread) == FAILED) {return FAILED;}
sleep(WAIT_TIME);
if (try_cancel_thread(&child_thread) == FAILED) {return FAILED;}
printf("the execution is succefully completed\n");
return SUCCESS;
}
#undef SUCCESS
#undef FAILED
#undef WAIT_TIME
#undef PRINT_DELTA_TIME
output:
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
Child thread text
the execution is succefully completed

GFileMonitor - g_signal_handler_block "changed" signal doesn't block handler?

All,
This may take a moment to set up. I have a small editor project I've worked on the past several months[1]. I originally wanted to implement an inotify watch on the current editor file to protect against modification by a foreing process. I created a custom signal, and routines to add, remove, and monitor (with pselect) the watch and routines to block and unblock emission of the custom signal to allow for normal save/save as without triggering the callback. The problem I had was how to add the monitor to the gtk_event_loop so that a check was performed on each iteration of the loop. I settled on using g_idle_add and went to the gtk-app-devel list to determine if the approach was reasonable or if there was a better way. The concensus was to use GIO/GFileMonitor instead of working with inotify directly.
Fast forward to the current problem. I rewrote the implementation to use GFileMonitor/g_file_monitor_file and rewrote the block and unblock routines to block handling of the "changed" signal to allow a normal save/save as without firing the callback. The problem is when I block the instance and handler_id for the callback before saving the file, the callback still fires. When using the inotify implementation with a custom signal, blocking the emission of the signal worked great. I have posted this back to the gtk-app-devel list, but have received nothing in return -- that's why I'm asking here. Why does g_signal_handler_block with the GIO/GFileMonitor not block handling to the "changed" signal callback? (more importantly, how do I fix it)
note: (MCVE - complete test code is at https://github.com/drankinatty/gtktest). To build with GtkSourceView2, simply type make with=-DWGTKSOURCEVIEW2, it will build as bin/gtkwrite, otherwise to build without, simply type make and it will build as bin/gtkedit.
The logic of the relevant code is as follows (app is an instance of the struct holding relevant editor variables/info and settings) The GIO/GFileMonitor implementation is in gtk_filemon.[ch] and the wrapper around the save function is in gtk_filebuf.c:
typedef struct {
...
gchar *filename;
GFileMonitor *filemon;
gulong mfp_handler;
...
} kwinst;
kwinst *app = g_slice_new (kwinst);
I set the watch with:
GFile *gfile = g_file_new_for_path (app->filename);
...
/* create monitor for app->filename */
app->filemon = g_file_monitor_file (gfile,
G_FILE_MONITOR_NONE,
cancellable, &err);
...
/* connect changed signal to monitored file saving ID */
app->mfp_handler = g_signal_connect (G_OBJECT(app->filemon), "changed",
G_CALLBACK (file_monitor_on_changed), data);
Both the instance (app->filemon) and handler_id (app->mfp_handler) are saved. (mfp is just short for modified by foreign process) In order to prevent handling of changes during normal save/save as operations, I created block and unblock functions to prevent the changes to the file from firing the callback, e.g. show below with debug g_print calls:
void file_monitor_block_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_print ("blocking changed (%lu)\n", app->mfp_handler);
g_signal_handler_block (app->filemon, app->mfp_handler);
}
void file_monitor_unblock_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_print ("unblocking changed (%lu)\n", app->mfp_handler);
g_signal_handler_unblock (app->filemon, app->mfp_handler);
}
To implement the block/unblock, I wrap the file 'save/save as' function with the block, then save[2], then unblock, but the callback is still firing on normal saves. e.g. the relevant part of the save function I have is:
if (app->mfp_handler) /* current file monitor on file */
file_monitor_block_changed (app); /* block "changed" signal */
g_print (" buffer_write_file (app, filename)\n");
buffer_write_file (app, filename); /* write to file app->filename */
if (filename)
file_monitor_add (app); /* setup monitoring on new name */
else if (app->mfp_handler)
file_monitor_unblock_changed (app); /* unblock "changed" signal */
With the debug g_print statements above, issuing save results in the following output:
$ ./bin/gtkwrite
blocking changed (669)
buffer_write_file (app, filename)
unblocking changed (669)
Monitor Event: File = /home/david/tmp/foo.txt.UY9IXY
G_FILE_MONITOR_EVENT_DELETED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CREATED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED
It makes no difference whether I include the block/unblock, the firing of the callback is unchanged. I suspect that the problem lies in the fact that the "changed" signal and it handling are implemented through GIO rather than GTK (as the custom signal for the inotify implementation was) and there being some difference in the abiliby to block handling of the "changed" signal using g_signal_handler_block -- though I cannot find a distinction in the documentation. The documentation states:
To get informed about changes to the file or directory you are monitoring,
connect to the “changed” signal. The signal will be emitted in the thread-default
main context of the thread that the monitor was created in (though if the global
default main context is blocked, this may cause notifications to be blocked
even if the thread-default context is still running).
https://developer.gnome.org/gio/stable/GFile.html#g-file-monitor-file
The application itself makes no use of any explicit threading and does not fork any part of the save. So I'm at a loss as to why the block/unblock does not prevent handling of the "changed" signal. The save is completely wrapped in the block/unblock, unless the g_file_set_contents call is asyncronous, there shouldn't be any timing issues I can see.
Why would calls to g_signal_handler_block/g_signal_handler_unblock fail to prevent handling of the "changed" signal emitted on changes to the current file? I can g_signal_handler_disconnect and nothing fires, but I shouldn't have to disconnect to temporarily block handling. What am I missing?
For completeness the file_monitor_on_changed function is included below along with the footnotes:
void file_monitor_on_changed (GFileMonitor *mon,
GFile *file, GFile *other,
GFileMonitorEvent evtype,
gpointer data)
{
kwinst *app = (kwinst *)data;
g_print ("Monitor Event: File = %s\n", g_file_get_parse_name (file));
switch (evtype)
{
case G_FILE_MONITOR_EVENT_CHANGED:
/* prompt or emit custom signal modified by foreign process */
g_print ("G_FILE_MONITOR_EVENT_CHANGED\n");
break;
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
g_print ("G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT\n");
break;
case G_FILE_MONITOR_EVENT_DELETED:
/* avoid firing on normal '.tmp' file delete */
if (g_strcmp0 (g_file_get_parse_name (file), app->filename)) {
g_print (" ignoring 'tmp' file delete.\n");
break;
}
/* prompt or emit custom signal modified by foreign process */
g_print ("G_FILE_MONITOR_EVENT_DELETED\n");
/* prompt save file */
break;
case G_FILE_MONITOR_EVENT_CREATED:
g_print ("G_FILE_MONITOR_EVENT_CREATED\n");
break;
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
g_print ("G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED\n");
break;
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
g_print ("G_FILE_MONITOR_EVENT_PRE_UNMOUNT\n");
break;
case G_FILE_MONITOR_EVENT_UNMOUNTED:
g_print ("G_FILE_MONITOR_EVENT_UNMOUNTED\n");
break;
case G_FILE_MONITOR_EVENT_MOVED:
g_print ("G_FILE_MONITOR_EVENT_MOVED\n");
/* prompt save file */
break;
case G_FILE_MONITOR_EVENT_RENAMED:
/* prompt save file */
g_print ("G_FILE_MONITOR_EVENT_RENAMED\n");
break;
case G_FILE_MONITOR_EVENT_MOVED_IN:
g_print ("G_FILE_MONITOR_EVENT_MOVED_IN\n");
break;
case G_FILE_MONITOR_EVENT_MOVED_OUT:
g_print ("G_FILE_MONITOR_EVENT_MOVED_OUT\n");
break;
default:
g_print ("unknown EVENT on changed signal.\n");
}
if (mon || other) {}
}
Using g_signal_handler_block works fine in other cases
As a point brought up in the comments, I can confirm I can easily block, unblock other signal handlers without issues. Specifically, when working with the inotify implementation, I created the following custom signal:
/* create signal to monitor file modified by foreign process */
app->SIG_MODFP = g_signal_new ("modified-foreign",
GTK_TYPE_TEXT_BUFFER,
GTK_RUN_ACTION,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_POINTER);
Then connecting the signal to a handler and saving the handler_id as follows:
/* custom signals */
app->mfp_handler2 = g_signal_connect (GTK_TEXT_BUFFER(app->buffer),
"modified-foreign",
G_CALLBACK (on_modified_foreign), app);
The on_modified_foreign callback is a simple test callback for purposes of testing the block/unblock:
void on_modified_foreign (GtkTextBuffer *buffer,
kwinst *app)
{
dlg_info ("File has changed on disk, reload?", "Modified by Foreign Process");
if (buffer || app) {}
}
Above dlg_info is just a wrapper to gtk_message_dialog_new to pop up a dialog when the "modified-foreign" signal is emitted.
Then implementing a simple test where one menuitem causes the signal to be emitted, e.g.:
void menu_status_bigredbtn_activate (GtkMenuItem *menuitem, kwinst *app)
{
g_signal_emit_by_name (G_OBJECT(app->buffer), "modified-foreign::", app);
}
And, finally blocking/unblocking works just fine:
void menu_status_block_activate (GtkMenuItem *menuitem, kwinst *app)
{
if (!app->mfp_handler2) return;
GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
g_signal_handler_block (buffer, app->mfp_handler2);
}
void menu_status_unblock_activate (GtkMenuItem *menuitem, kwinst *app)
{
if (!app->mfp_handler2) return;
GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
g_signal_handler_unblock (buffer, app->mfp_handler2);
}
It all works fine. Select the bigredbtn menu item, the signal is emitted and up pops the dialog. Then selecting the block menu item and then again trying the bigredbtn -- nothing happens, no dialog, no nothing. Then selecting the unblock menu item and again selecting the bigredbtn, the dialog again pops up on each selection. (and the signals emitted while the handler was blocked were not queued and do not invoke the handler once it is unblocked)
So that is where I'm stuck. A large part of the problem is without picking though the GIO source line-by-line it, to a large extent, is a big black box. All works fine on the GTK side, but when doing the same thing involving a GIO function, the results seem not to work as expected.
Thanks for any other insight you may have on this issue.
footnote 1: https://github.com/drankinatty/gtkwrite
footnote 2: buffer_write_file calls g_file_set_contents to write to disk.
Ok, I've played with it long enough, and I've found a solution. The key seems to be, for whatever reason in the grand scheme of GIO, to call the block and unblock from within the same source that the signals were originally connected in. For example, adding block/unblock functions within the gtk_filemon.c source and then calling from anywhere (as done from a test menu item above) works fine, e.g.:
void file_monitor_block_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_signal_handler_block (app->filemon, app->mfp_handler);
}
void file_monitor_unblock_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_signal_handler_unblock (app->filemon, app->mfp_handler);
}
This allows the calls from the above two functions in menu_status_block_activate and menu_status_unblock_activate to work as intended and successfully block handling of the "changed" signal while blocked, and resume when unblocked. Why it cannot be done by direct call to g_signal_handler_block and g_signal_handler_unblock using the instance and handler_id will just have to remain a documentation mystery for the time being.
note: I'll leave the github.com gtktest code up until mid-April if anyone wants to play with it, thereafter the working code will exist at https://github.com/drankinatty/gtkwrite.

Safety of using pthreads in Gtk+2.0 application

I have a simple multithreaded Gtk+2.0 application that acquires data from multiple sources (microphone, webcam, temperature sensor), and displays data from these as images on screen (webcam frame grabs, microphone data represented as oscilloscope renders, text, etc).
It's to my understanding from the Gtk manual and various articles that only the main processing thread should use any Gtk functions/calls that affect the UI. However, the main() entry point blocks on gtk_main() until I close the UI. With the exception of event handlers that are mapped to things like when I click on a button or slider in my UI, it seems the only option left open to me is to spawn a few pthreads and have them do the periodic sampling of data and updating information on-screen in the UI.
I remember from doing some MFC GUI development a long ways back that a similar principle applied: only a single specific thread should be updating the UI elements. How do I accomplish this in C with Gtk+2.0?
Thank you.
According to the documentation, the main even loop can accept sources from different threads:
A GMainContext can only be running in a single thread, but sources can
be added to it and removed from it from other threads.
So you can inject the code in the UI thread from your working thread by:
creating a GSource (e.g., by using g_idle_source_new);
adding the code you want to be executed with g-source-set-callback;
attaching it to the UI thread context with g_source_attach().
Here is a sample program for GTK+2 and GLib >= 2.32:
#include <gtk/gtk.h>
#define N_THREADS 100
#define N_ITERATIONS 100
GtkWidget *bar;
GMainContext *context;
static gboolean
update_progress_bar(gpointer user_data)
{
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar),
g_random_double_range(0, 1));
return G_SOURCE_REMOVE;
}
static gpointer
thread_func(gpointer user_data)
{
int n_thread = GPOINTER_TO_INT(user_data);
int n;
GSource *source;
g_print("Starting thread %d\n", n_thread);
for (n = 0; n < N_ITERATIONS; ++n) {
/* If you want to see anything you should add a delay
* to let the main loop update the UI, e.g.:
* g_usleep(g_random_int_range(1234, 567890));
*/
source = g_idle_source_new();
g_source_set_callback(source, update_progress_bar, NULL, NULL);
g_source_attach(source, context);
g_source_unref(source);
}
g_print("Ending thread %d\n", n_thread);
return NULL;
}
gint
main(gint argc, gchar *argv[])
{
GtkWidget *window;
GThread *thread[N_THREADS];
int n;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
bar = gtk_progress_bar_new();
gtk_container_add(GTK_CONTAINER(window), bar);
context = g_main_context_default();
for (n = 0; n < N_THREADS; ++n)
thread[n] = g_thread_new(NULL, thread_func, GINT_TO_POINTER(n));
gtk_widget_show_all(window);
gtk_main();
for (n = 0; n < N_THREADS; ++n)
g_thread_join(thread[n]);
return 0;
}
I would do your sampling in separate threads, as you suggest. The question then is how you update the UI. What I would do is to use a 'self-pipe'. This is normally done to communicate from signal handlers, but it works just fine between threads when one of the threads can't wait on a condition variable. What you do here is set up a dedicated pipe, write one character to the pipe in the thread that has got the data, and select() on the read end of the pipe in the main program's select() loop. Details here: Using self-pipe, how can I avoid that the event loop stalls on read()? - useful background on its origins here.
You can then make GTK+ listen on the read end of the pipe. Your problem is thus reduced to making GTK+ respond to something on an FD - see here (first answer) for how.

Using GTK and C, how can I start/stop a long calculation (in a seperate thread) using a button?

Using GTK and C, how can I start/stop a long calculation (in a seperate thread) using a button? I have working code that does just that but I have little confidence that it isa reasonable method (i.e., "right").
I have a single button whose label toggles from "start" to "stop". I also have a global pthread_t variable to store a thread. My approach is to either launch or cancel a thread through the button's clicked signal handler depending on the value of a global boolean-like "idle" flag which indicates if the thread is currently running or not.
I wanted a working well-designed minimum test case so that I can easily understand the code to adapt for a larger program. This question is very similar to Python&PyGTK: Stop while on button click but that question is in python which I don't know.
My code --- posted below --- seems to work but I'm not confident in it because I can easily bring the system to its knees by just clicking the start/stop button a few times in rapid succession.
I'd be curious to see how others would (independently) solve this, how their approach compares to mine, and also a code-review for my own approach if it is actually a decent way.
#include <gtk/gtk.h>
#include <pthread.h>
/* suppress unused variable warnings */
#define UNUSED(x) (void)(x)
typedef struct _Data {
GtkWidget *window1,
*button1;
gint idle;
pthread_t calcthread;
} Data;
static Data *data;
void *calcfunc(void *arg) {
int i;
UNUSED(arg);
data->idle=FALSE;
gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
/* This is intended to simulated a long calculation that may finish.
Adjust the limit as needed */
for(i=1;i<2e9;++i) {
}
data->idle=TRUE;
pthread_exit(NULL);
}
/* this is our click event handler.... it suppose to start or stop
the "calcthread" depending on the value of the "idle" flag */
void on_button1_clicked(GtkWidget *widget, Data *ldata) {
int ret;
UNUSED(widget);
UNUSED(ldata);
if ( data->idle==TRUE ) {
printf("idle.. starting thread\n");
ret=pthread_create( &data->calcthread, NULL, calcfunc, NULL);
if ( ret !=0 ) {
g_error("ERROR: could not create thread\n");
}
} else {
printf("not idle... canceling thread...");
ret= pthread_cancel( data->calcthread );
if ( ret != 0 ) {
g_error("ERROR: could not cancel thread\n");
} else {
printf("canceled\n");
}
data->idle=TRUE;
gtk_button_set_label(GTK_BUTTON(data->button1),"start");
}
}
/* just defines our setup */
int main (int argc, char *argv[]) {
g_thread_init(NULL);
gdk_threads_init();
gdk_threads_enter();
gtk_init(&argc, &argv);
data=g_slice_new0(Data);
data->idle=TRUE; /* initial state */
printf("idle is %d\n",data->idle);
/* add widgets and objects to our structure */
data->window1=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(data->window1),250,250);
data->button1=gtk_button_new_with_label("Start");
gtk_container_add(GTK_CONTAINER(data->window1),GTK_WIDGET(data->button1));
gtk_signal_connect(GTK_OBJECT(data->window1), "delete-event",
gtk_main_quit, NULL);
gtk_signal_connect(GTK_OBJECT(data->button1), "clicked",
G_CALLBACK(on_button1_clicked), NULL);
gtk_widget_show_all(GTK_WIDGET(data->window1));
gtk_main();
/* Don't forget to free the memory! */
g_slice_free(Data, data);
gdk_threads_leave();
return 0;
}
As you are calling GTK functions from the secondary thread you need to wrap the call to
gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
with gdk_threads_enter/gdk_threads_leave calls. However, it is better practice to only call GTK functions from one thread. The easiest way is with an idle function using g_idle_add as this will be called from the main thread, however in your case you could just move the call to gtk_button_set_label from calcfunc into on_button1_clicked.
You should also set data->idle = FALSE in the on_button1_clicked handler to solve the race condition where you click the button too quickly.
Another way you could do this is without threads and that is to run the GTK main loop during the long operation. In your loop you just need to pump the Gtk event loop.
for(i=1;i<2e9;++i) {
while (gtk_events_pending ()) {
gtk_main_iteration ();
}
}
This means you avoid all the threading problems and needing to lock data access. You could stop the calculation by checking a boolean value each iteration which gets set in the on_button1_clicked handler.
The following code does what I asked. It uses pthreads. I don't know if it's the most elegant but it seems to work. The trick was using two flags: one for the idle state and one for a cancel request, which avoids needing to cancel the thread using the "pthread_cancel" function, which I find to be unusual in real code.
#include <gtk/gtk.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#define UNUSED(x) (void)(x)
#define handle_error_en(en, msg) do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
typedef struct _Data {
GtkWidget *window1,
*button1;
} Data;
static Data *data;
static pthread_mutex_t calcmutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t calcthread=0;
static gboolean idle=TRUE,cancel_request=FALSE;
void *calcfunc(void *arg) {
int i,s;
UNUSED(arg);
g_print("\tstarting thread\n");
s = pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
if (s != 0) {
handle_error_en(s, "pthread_setcancelstate");
}
gdk_threads_enter();
gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
gdk_threads_leave();
g_print("\tstarting work...\n");
for (i=0; i<100000000 ;++i) {
/* check for cancelation */
pthread_mutex_lock(&calcmutex);
if ( cancel_request ) {
g_print("\t[cancel request noted].\n");
pthread_mutex_unlock(&calcmutex);
break;
}
pthread_mutex_unlock(&calcmutex);
/* do "calculation" */
i=i*1*-1*1*-1;
}
g_print("\tdone work.\n");
gdk_threads_enter();
gtk_button_set_label(GTK_BUTTON(data->button1),"Start");
gdk_threads_leave();
pthread_mutex_lock(&calcmutex);
cancel_request=FALSE;
idle=TRUE;
pthread_mutex_unlock(&calcmutex);
g_print("\tdone thread.\n");
pthread_exit(NULL);
}
void on_button1_clicked(GtkWidget *widget, gpointer *ldata) {
int s;
UNUSED(widget);
UNUSED(ldata);
g_print("entered on_button1_clicked\n");
pthread_mutex_lock(&calcmutex);
if ( idle ) {
g_print("idle, starting thread\n");
s = pthread_create(&calcthread, NULL, calcfunc, NULL);
if (s != 0) {
handle_error_en(s, "pthread_create");
}
idle=FALSE;
} else {
g_print("not idle and not first time, making canceling request.\n");
cancel_request=TRUE;
}
pthread_mutex_unlock(&calcmutex);
g_print("finished on_button1_clicked\n");
}
/* just defines our setup */
int main (int argc, char *argv[]) {
g_thread_init(NULL);
gdk_threads_init();
gdk_threads_enter();
gtk_init(&argc, &argv);
data=g_slice_new0(Data);
printf("initial idle is %d\n",idle);
/* add widgets and objects to our structure */
data->window1=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(data->window1),250,250);
data->button1=gtk_button_new_with_label("Start");
gtk_container_add(GTK_CONTAINER(data->window1),GTK_WIDGET(data->button1));
gtk_signal_connect(GTK_OBJECT(data->window1), "delete-event",
gtk_main_quit, NULL);
gtk_signal_connect(GTK_OBJECT(data->button1), "clicked",
G_CALLBACK(on_button1_clicked), NULL);
gtk_widget_show_all(GTK_WIDGET(data->window1));
gtk_main();
/* free the memory and stuff */
g_slice_free(Data, data);
pthread_mutex_destroy(&calcmutex);
gdk_threads_leave();
return 0;
}
This compiles warningless with
gcc -Wall -Wextra -Wconversion -pedantic `pkg-config --cflags --libs gtk+-2.0` start_stop.c -o start_stop
Well in Java I would call interrupt() on that Thread, the thread would then get an InterruptedException, and would be able to clean up in its exception handler's catch or finally block before exiting.
In C there is several options:
send that thread a signal with kill(), and have the signal handler longjmp() to a point in your code where you previously called setjmp(). For cleanup, you'd just do something when setjmp() returns non-zero, meaning it's resuming from the subsequent longjmp() call.
call pthread_cancel(). The only real cleanup you get here is that the cancellation handlers you previously registered with pthread_cleanup_push() will get called.
have either a volatile variable or a lock protected variable that gets checked periodically (for example once every loop iteration) and set to some value when the calculation should be canceled. Cleanup is easy because you can do whatever you like when the flag is set and you break out of the loop.
I dislike all of them: signals mean you have to handle partial failures (eg. short reads and writes on files and sockets) and errno==EINTR correctly everywhere in your code while avoiding all kinds of gotchas that exist with signal handlers (such as the small stack size and limits on what system calls are safe to use), pthread_cancel() means you have to drag state around between the thread function and the cancellation handlers, and the flag can impact performance since it has to read uncached memory or take a lock every time and if you don't check it often enough then the thread won't respond immediately when the flag is set.

how to wait for starting thread to have executed init code

I'm having trouble synchronizing a master thread to a recently started child thread.
What I want to do is:
master thread creates a new child thread and blocks
child thread starts and initializes (might take some time)
once the child thread is initialized, the main thread continues (and the two threads run in parallel)
My first attempt was something like:
typedef struct threaddata_ {
int running;
} threaddata_t;
void*child_thread(void*arg) {
threaddata_t*x=(threaddata_t)arg;
/* ... INITIALIZE ... */
x->running=1; /* signal that we are running */
/* CHILD THREAD BODY */
return 0;
}
void start_thread(void) {
threaddata_t*x=(threaddata_t*)malloc(sizeof(threaddata_t));
x->running=0;
int result=pthread_create(&threadid, 0, child_thread, &running);
while(!x->running) usleep(100); /* wait till child is initialized */
/* MAIN THREAD BODY */
}
Now I didn't like this at all, because it forces the main thread to sleep for probably a longer period than necessary.
So I did a 2nd attempt, using mutexes&conditions
typedef struct threaddata_ {
pthread_mutex_t x_mutex;
pthread_cond_t x_cond;
} threaddata_t;
void*child_thread(void*arg) {
threaddata_t*x=(threaddata_t)arg;
/* ... INITIALIZE ... */
pthread_cond_signal(&x->x_cond); /* signal that we are running */
/* CHILD THREAD BODY */
return 0;
}
void start_thread(void) {
threaddata_t*x=(threaddata_t*)malloc(sizeof(threaddata_t));
pthread_mutex_init(&x->x_mutex, 0);
pthread_cond_init (&x->x_cond , 0);
pthread_mutex_lock(&x->x_mutex);
int result=pthread_create(&threadid, 0, child_thread, &running);
if(!result)pthread_cond_wait(&x->x_cond, &x->x_mutex);
pthread_mutex_unlock(&x->x_mutex);
/* MAIN THREAD BODY */
}
This seemed more sane than the first attempt (using proper signals rather than rolling my own wait loop), until I discovered, that this includes a race condition:
If the child thread has finished the initialization fast enough (before the main thread waits for the condition), it will deadlock the main thread.
I guess that my case is not so uncommon, so there must be a really easy solution, but I cannot see it right now.
Proper way of condvar/mutex pair usage:
bool initialised = false;
mutex mt;
convar cv;
void *thread_proc(void *)
{
...
mt.lock();
initialised = true;
cv.signal();
mt.unlock();
}
int main()
{
...
mt.lock();
while(!initialised) cv.wait(mt);
mt.unlock();
}
This algorithm avoids any possible races. You can use any complex condition modified when mutex locked (instead of the simple !initialised).
The correct tool for that are sem_t. The main thread would initialize them with 0 and wait until it receives a token from the newly launched thread.
BTW your mutex/cond solution has a race condition because the child thread is not locking the mutex.
A barrier should do the trick nicely. Since you mention the need for support on Win32 in comments, barriers are supported on the latest Win32 pthreads, so you shouldn't have to write your own wrapper to get some portability between Win32 and *nix.
Something like:
typedef struct threaddata_ {
pthread_barrier_t* pbarrier;
} threaddata_t;
void* child_thread(void*arg) {
threaddata_t*x=(threaddata_t*)arg;
/* ... INITIALIZE ... */
int result = pthread_barrier_wait(x->pbarrier);
/* CHILD THREAD BODY */
return 0;
}
void start_thread(void) {
pthread_barrier_t barrier;
int result = pthread_barrier_init(&barrier, NULL, 2);
threaddata_t*x=(threaddata_t*)malloc(sizeof(threaddata_t));
x->pbarrier = &barrier;
int result=pthread_create(&threadid, 0, child_thread, &x);
result = pthread_barrier_wait(&barrier);
/* child has reached the barrier */
pthread_barrier_destroy(&barrier); /* note: the child thread should not use */
/* the barrier pointer after it returns from*/
/* pthread_barrier_wait() */
/* MAIN THREAD BODY */
}
The drawback to this solution is that it may needlessly blocks the child thread momentarily. If that's a problem, the condition variable solution mentioned by Dmitry Poroh is the way to go.

Resources