GTKMM GUI With Worker Threads

For quite a while now I have wanted a good solution to the problem of ensuring that the SimSoup user interface is active while the simulation is running.

I have tried various options, with varying degrees of success. I knew that a good solution would entail multi-threading, but didn't know enough to implement it. Finally I think I have it figured out. Here is a demo program showing the basic technique. It is for the GTKMM C++ interface to Gtk that SimSoup uses.

//    GUI_With_WorkThreads.h
//
//    This program is free software; you can redistribute it and/or 
//    modify it under the terms of the GNU General Public 
//    License Version 3. 

//    Copyright (C) 2011 Chris Gordon-Smith

#ifndef GUI_WITH_WORKTHREADS
#define GUI_WITH_WORKTHREADS

class GUI_With_WorkThreads : public Gtk::Window
{
  public:
    GUI_With_WorkThreads();
    virtual ~GUI_With_WorkThreads();

  private: 
    //  Disable copy construction
    GUI_With_WorkThreads(GUI_With_WorkThreads const & );
    GUI_With_WorkThreads const & 
        operator=(GUI_With_WorkThreads const & );

    // GUI Wigets
    Gtk::VBox        Main_VBox_;
    Gtk::Button      GUI_Thread_Button_;
    Gtk::Label       GUI_Thread_Button_Press_Counter_Label_;
    Gtk::Label       WorkThread1_Counter_Label_;
    Gtk::Label       WorkThread2_Counter_Label_;

    Gtk::Button      WorkThread1_StartStop_Button_;
    Gtk::Button      WorkThread2_StartStop_Button_;
    Gtk::Button      Exit_Button_;

    // Thread Progress Counters and Active/Inactive Flags
    int WorkThread1_Counter_;
    int WorkThread2_Counter_;
    int GUI_Thread_Counter_;
    bool WorkThread1_Active_Fl_;
    bool WorkThread2_Active_Fl_;

    // Pointers to the Worker Threads
    Glib::Thread* WorkThread1_Pt;
    Glib::Thread* WorkThread2_Pt;

    // Dispatcher objects for the WorkThread Counters
    Glib::Dispatcher WorkThread1_Counter_Disp_;
    Glib::Dispatcher WorkThread2_Counter_Disp_;

    //  Event Handlers running in GUI Thread
    void On_GUI_Thread_Button_Click(); 
    void On_WorkThread1_Counter_Event();
    void On_WorkThread2_Counter_Event();
    void On_WorkThread1_StartStop_Click();
    void On_WorkThread2_StartStop_Click();
    void On_Exit_Click();
    
    // Worker Thread Functions
    void WorkThread1();   // Runs in Non-GUI thread
    void WorkThread2();   // Runs in Non-GUI thread
};

#endif //GUI_WITH_WORKTHREADS
//    GUI_With_WorkThreads.cpp
//
//    This program is free software; you can redistribute it and/or 
//    modify it under the terms of the GNU General Public License 
//    Version 3. 
// 
//    Copyright (C) 2011 Chris Gordon-Smith
//----------------------------------------------------------------------

// This small program demonstrates how a gtkmm based GUI application
// can have one or more non-GUI 'worker' threads. This is relevant in
// a number of scenarios. A typical example would be a simulation
// where the GUI should remain active while the simulation is running.

// To compile it you need gtkmm-2.4 installed. 
// If you are on GNU/Linux, then use pkg-config as follows:
// g++ -o GUI_With_WorkThreads   GUI_With_WorkThreads.cpp  
       `pkg-config  gtkmm-2.4  --libs --cflags`

// Key Concepts For Multi-Threading In This Program
// ================================================

// The Glib threading system is used. It has to be initialised with a
// call to Glib::thread_init(). The GUI code all runs in a 'GUI
// Thread'. This is the Glib thread that runs when the program is
// started. 

// Additional non-GUI Glib threads are started with
// Glib::Thread::create(). These are used for the Worker Threads.  The
// Worker Threads are created as 'joinable'. This means that the GUI
// thread can invoke the join function to wait for them (which it does
// here only to ensure that the application closes cleanly).

// A Worker Thread communicates with the GUI Thread using a
// Glib::Dispatcher object. This object must have been previously
// connected to an event handler that executes in the GUI Thread. This
// mechanism is used to enable a Worker Thread to tell the GUI Thread
// when thread progress information should be displayed on the
// GUI. The Worker Thread invokes its Dispatcher object, and this
// causes the connected handler in the GUI Thread to run.

// A GUI event handler executes in the Glib Thread that established the
// connection, and so the connections in this program are made by code
// running in the constructor.


#include <gtkmm.h>
#include <sstream>
#include "GUI_With_WorkThreads.h"

GUI_With_WorkThreads::GUI_With_WorkThreads()
    :  Main_VBox_(false,20),  
       GUI_Thread_Button_("GUI Thread"),
       WorkThread1_Counter_(0),
       WorkThread2_Counter_(0),
       GUI_Thread_Counter_(0),
       WorkThread1_Active_Fl_(true),  // Work Thread 1 is active on 
                                      // application start
       WorkThread2_Active_Fl_(false),
       WorkThread1_StartStop_Button_("Start / Stop Thread 1"),
       WorkThread2_StartStop_Button_("Start / Stop Thread 2"),
       GUI_Thread_Button_Press_Counter_Label_
           ("GUI Thread Button Clicks:  0"),
       WorkThread1_Counter_Label_("Thread 1 Progress:  0"),
       WorkThread2_Counter_Label_("Thread 2 Progress:  0"),
       Exit_Button_("Exit"),
       WorkThread1_Pt(0),
       WorkThread2_Pt(0)

// Constructor: Executes in the GUI Thread
{
    add(Main_VBox_); 
    
    // Setup GUI Widgets
    Main_VBox_.set_border_width(15); 
    Main_VBox_.pack_start(GUI_Thread_Button_Press_Counter_Label_); 
    Main_VBox_.pack_start(GUI_Thread_Button_); 

    Main_VBox_.pack_start(WorkThread1_Counter_Label_);  
    Main_VBox_.pack_start(WorkThread1_StartStop_Button_);

    Main_VBox_.pack_start(WorkThread2_Counter_Label_);  
    Main_VBox_.pack_start(WorkThread2_StartStop_Button_);
    
    Main_VBox_.pack_start(Exit_Button_);

    //  Connect the handler for the GUI Thread Button Click event
    GUI_Thread_Button_.signal_clicked().connect(
        sigc::mem_fun(*this,
              &GUI_With_WorkThreads::On_GUI_Thread_Button_Click));

    //  Connect the handlers for updates to WorkThread progress
    //  counters. These handlers run in the GUI Thread when the Work
    //  Threads invoke their Glib::Dispatcher objects.
    WorkThread1_Counter_Disp_.connect( 
        sigc::mem_fun(*this, 
              &GUI_With_WorkThreads::On_WorkThread1_Counter_Event));
    WorkThread2_Counter_Disp_.connect( 
        sigc::mem_fun(*this, 
              &GUI_With_WorkThreads::On_WorkThread2_Counter_Event));

    //  Connect the handlers for the Start/Stop buttons for the Work
    //  Threads
    WorkThread1_StartStop_Button_.signal_clicked().connect(
        sigc::mem_fun(*this, 
              &GUI_With_WorkThreads::On_WorkThread1_StartStop_Click));
    WorkThread2_StartStop_Button_.signal_clicked().connect(
        sigc::mem_fun(*this, 
              &GUI_With_WorkThreads::On_WorkThread2_StartStop_Click));

    //  Create joinable thread for the Work Threads that are acive on
    //  application startup
    if(WorkThread1_Active_Fl_)
    WorkThread1_Pt = Glib::Thread::create( 
        sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread1),true);
    if(WorkThread2_Active_Fl_)
    WorkThread1_Pt = Glib::Thread::create( 
        sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread2),true);

    //  Connect the handler for program exit
    Exit_Button_.signal_clicked().connect(
        sigc::mem_fun(*this, 
                      &GUI_With_WorkThreads::On_Exit_Click));

    show_all();
}
//-----------------------------------------------------------------

void  GUI_With_WorkThreads::On_WorkThread1_Counter_Event()

//  Update the WorkThread 1 Progress Counter on the GUI. This function
//  executes in the GUI Thread
{
    std::ostringstream oss;    
    oss << "Thread 1 Progress:  " << WorkThread1_Counter_++;
    WorkThread1_Counter_Label_.set_text(oss.str());
}
                                     //---------------------------------

void  GUI_With_WorkThreads::On_WorkThread2_Counter_Event()

//  Update the WorkThread 2 Progress Counter on the GUI. This fuction
//  executes in the GUI Thread
{
    std::ostringstream oss;    
    oss <<  "Thread 2 Progress:  " << WorkThread2_Counter_++;
    WorkThread2_Counter_Label_.set_text(oss.str());
}
//----------------------------------------------------------------------

void GUI_With_WorkThreads::On_WorkThread1_StartStop_Click()

// Start / Stop Work Thread 1. If starting, create a new Glib::Thread
// This function executes in the GUI Thread
{
    WorkThread1_Active_Fl_ = !WorkThread1_Active_Fl_;

//  Create a joinable Glib::Thread for Work Thread 1
    WorkThread1_Pt = Glib::Thread::create( 
        sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread1),true);
}
//----------------------------------------------------------------------

void GUI_With_WorkThreads::On_WorkThread2_StartStop_Click()    
// Start / Stop Work Thread 2. If starting, create a new Glib::Thread
// This function executes in the GUI Thread
{
    WorkThread2_Active_Fl_ = !WorkThread2_Active_Fl_;
    
//  Create a joinable Glib thread for Work Thread 2
    WorkThread2_Pt = Glib::Thread::create( 
        sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread2),true);
}
//----------------------------------------------------------------------

GUI_With_WorkThreads::~GUI_With_WorkThreads()
//  If the program window is exited using the window manager to close
//  its window, then exit cleanly anyway
{
    On_Exit_Click();
}
//  --------------------------------------------------------------------

void GUI_With_WorkThreads::On_GUI_Thread_Button_Click()
//  Update and display the GUI Thread progress counter
//  This function executes in the GUI Thread
{
    std::ostringstream oss;    
    oss << "GUI Thread Button Clicks:  " << ++GUI_Thread_Counter_;
    GUI_Thread_Button_Press_Counter_Label_.set_text(oss.str());
}
//----------------------------------------------------------------------

void GUI_With_WorkThreads::WorkThread1()
//  This is the main loop for Work Thread 1. It runs in a Non GUI
//  Thread. So long as Work Thread 1 is active, it loops and keeps the
//  GUI Thread updated on progress.
{
    while(WorkThread1_Active_Fl_)
    {
        for(int Iloop = 0; Iloop < 1000000; Iloop++) 
        {
            // We could do some work here!
        }

        // Invoke the GUI Thread to display the progress counter for
        // this Work Thread
        WorkThread1_Counter_Disp_();
    }
} 	
//----------------------------------------------------------------------

void GUI_With_WorkThreads::WorkThread2()

//  This is the main loop for Work Thread 2. It runs in a Non GUI
//  Thread. So long as Work Thread 2 is active, it loops and keeps the
//  GUI Thread updated on progress.
{
    while(WorkThread2_Active_Fl_)
    {
        for(int Iloop = 0; Iloop< 1000000;  Iloop++) 
        {      
            // We could do some work here!
        }

        // Invoke the GUI Thread to display the progress counter for
        // this Work Thread
        WorkThread2_Counter_Disp_();
    }
} 	
//----------------------------------------------------------------------

void GUI_With_WorkThreads::On_Exit_Click()

// If the user presses exit, inactivate the Worker Threads, wait for
// them to complete, and exit the program.
{
    WorkThread1_Active_Fl_ = false;
    WorkThread2_Active_Fl_ = false;

    if (WorkThread1_Pt) WorkThread1_Pt->join();
    if (WorkThread2_Pt) WorkThread2_Pt->join();
    exit(0);
}
//----------------------------------------------------------------------

int main(int argc, char**argv)
{
    Glib::thread_init();  // Initialise the multithreading system
    Gtk::Main Application(argc,argv);
    GUI_With_WorkThreads Application_Window;
    Application.run(Application_Window);
    return EXIT_SUCCESS;   
}