Hi, In order to deal in part with the issues thrown up by the bug report at https://bugzilla.gnome.org/show_bug.cgi?id=512348 , I have agreed to contribute a new section to the gtkmm tutorial on writing multi-threaded programs using gtkmm. This will reduce the risk of too many people writing thread unsafe code because of the (partly hidden) problems associated with libsigc++.
The text set out below (which for ease of reading is at this stage in plain text rather than formatted XML) is drawn from my own experience in writing multi-threaded code and in going through the tedious business of inspecting libsigc++ in order to determine what is safe and what is not. However, I know I am not the only person to have been faced with this so if anyone else has any gotchas which they have come across and which I have not mentioned, or suggestions arising from their own experience, I would like to hear about them. Regards, Chris *********************** Writing multi-threaded programs using gtkmm The constraints Programs employing multiple threads of execution can be written using gtkmm, as glibmm wraps the functions and objects provided by glib for threads, and so provides the normal set of thread launching functions, mutexes, condition variables and scoped locking classes required for writing multi-threaded programs using C++. Those using recent versions of g++'s C++0x implementation can also make use of the thread facilities to be provided by the forthcoming C++11/12 standard, in combination with glibmm and gtkmm. However, care is required arising from the fact that libsigc++ is not thread-safe. Amongst other things, a class inheriting from sigc::trackable will, via that inheritance, have a std::list object keeping track of slots connected to any of its non-static methods. Each sigc::slot object also keeps, via sigc::slot_rep, its own sigc::trackable object to track any sigc::connection objects which it needs to inform about its demise. sigc::signal objects also keep lists of slots, which will be updated with a call to its connect() method or calls to any sigc::connection object relating to such a connection. None of these are protected by mutexes or other means of synchronization. This requires a number of rules to be observed when writing multi-threaded programs using gtkmm: 1. Although code written for the X11 backend of GTK+ can use the GDK global lock, as invoked by gdk_threads_enter() and gdk_threads_leave(), so as to address GTK+ in more than one thread, because of libsigc++ this will not usually work with gtkmm. Instead, if a worker thread needs to invoke a gtkmm function, it should do so via Glib::Dispatcher. Glib::Dispatcher is the key to writing successful multi-threaded code using glibmm and gtkmm, and is dealt with in more detail below. (As it happens, this approach also provides a cleaner program structure than using the global lock.) 2. Unless special additional synchronization is employed, any particular sigc::signal object should be regarded as 'owned' by the thread which created it. Only that thread should connect slots with respect to the signal object, and only that thread should emit() or call operator()() on it. 3. Unless special additional synchronization is employed, any sigc::connection object should be regarded as 'owned' by the thread which created the slot and called the method which provided the sigc::connection object. Only that thread should call sigc::connection methods on the object. 4. A slot which references a non-static method of a class deriving from sigc::trackable should never be copied to another thread (one consequence of this is that Glib::Thread::create() should not be called with a slot argument which represents a non-static method of such a class). 5. If a particular class object derives from sigc::trackable, only one thread should create slots representing any of its non-static methods (that is, create slots with sigc::mem_fun()). The first thread to create such a slot should be regarded as owning the relevant object for the purpose of creating further slots referencing ANY of its non-static methods. 6. Although glib is itself thread-safe (assuming Glib::thread_init() has been called), any glibmm wrappers which use libsigc++ will not be. So for example, the only thread which should call Glib::SignalIdle::connect(), Glib::SignalIdle::connect_once(), Glib::SignalIO::connect(), Glib::SignalTimeout::connect(), Glib::SignalTimeout::connect_once(), Glib::SignalTimeout::connect_seconds, Glib::SignalTimeout::connect_seconds_once(), and manipulate any sigc::connection object returned by them, is the thread in which the main loop to which the connection is made runs. (The connect*_once() variants could be made thread-safe for any case where the slot does not relate to a non-static method of a class deriving from sigc::trackable, but that has not yet been implemented.) Point 5 above is particularly unintuitive. The overall message is: take extra care when deriving classes from sigc::trackable in multi-threaded programs. Using Glib::Dispatcher The slots connected to sigc::signal objects execute in the thread which calls emit() or operator()() on the signal. Glib::Dispatcher does not behave this way: instead its connected slots execute in the thread in which the Glib::Dispatcher object was constructed (which must have a glib main loop). If a Glib::Dispatcher object is constructed in the main GUI thread (which will therefore be the receiver thread), any worker thread can emit on it and have the connected slots safely execute gtkmm functions. Some thread-safety rules on the use of Glib::Dispatcher still apply: only the receiver thread (normally the main GUI thread) should call connect() on it, or manipulate any related sigc::connection object, unless additional synchronization is employed. However, any worker thread can safely emit on it without any locking once the receiver thread has connected the slots, provided that the Glib::Dispatcher object is constructed before the worker thread is started (if it is constructed after the thread has started, additional synchronization will normally be required to ensure visibility). Aside from the fact that connected slots always execute in the receiver thread, Glib::Dispatcher objects are similar to sigc::signal<void> objects. They therefore cannot pass unbound arguments nor return a value. The best way to pass unbound arguments is with a thread-safe (asynchronous) queue. At the time of writing glibmm does not have one, although most people writing multi-threaded code will have one available to them (they are relatively easy to write although there are subtleties in combining thread-safety with strong exception safety). A Glib::Dispatcher object can be emitted on by the receiving thread as well as by a worker thread, although this should be done within reasonable bounds. Glib::Dispatcher objects share a single common pipe, which could in theory at least fill up on a very heavily loaded system running a program with a very large number of Dispatcher objects in use. Were the pipe to fill up before the receiving thread's main loop has had an opportunity to read from it to empty it, and the receiving thread attempt to emit and so write to it when it is in that condition, the receiving thread would block on the write, so deadlocking. Where the receiving thread is to emit, a normal sigc::signal<void> object can of course be used instead. _______________________________________________ gtkmm-list mailing list [email protected] http://mail.gnome.org/mailman/listinfo/gtkmm-list
