Example
| Chapter 29. Multi-threaded programs |

## Example

This is an example program with two threads, one GUI thread, like in all gtkmm programs, and one worker thread. The worker thread is created when you press the `Start work` button. It is deleted when the work is finished, when you press the `Stop work` button, or when you press the `Quit` button.

A `Glib::Dispatcher` is used for sending notifications from the worker thread to the GUI thread. The `ExampleWorker` class contains data which is accessed by both threads. This data is protected by a `std::mutex`. Only the GUI thread updates the GUI.

Compiling and linking a multi-threaded program can require special compiler and linker options. If you use the g++ compiler, add the `-pthread` option. Other compilers may require other options. If you build with meson, it handles the multi-threading complications for you, if you add `dependency('threads')`.

Figure 29.1. Multi-Threaded Program

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "exampleworker.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

      // Called from the worker thread.
      void notify();

    private:
      // Signal handlers.
      void on_start_button_clicked();
      void on_stop_button_clicked();
      void on_quit_button_clicked();

      void update_start_stop_buttons();
      void update_widgets();

      // Dispatcher handler.
      void on_notification_from_worker_thread();

      // Member data.
      Gtk::Box m_VBox;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_ButtonStart;
      Gtk::Button m_ButtonStop;
      Gtk::Button m_ButtonQuit;
      Gtk::ProgressBar m_ProgressBar;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::Dispatcher m_Dispatcher;
      ExampleWorker m_Worker;
      std::thread* m_WorkerThread;
    };

    #endif // GTKMM_EXAMPLEWINDOW_H

File: `exampleworker.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWORKER_H
    #define GTKMM_EXAMPLEWORKER_H

    #include <gtkmm.h>
    #include <thread>
    #include <mutex>

    class ExampleWindow;

    class ExampleWorker
    {
    public:
      ExampleWorker();

      // Thread function.
      void do_work(ExampleWindow* caller);

      void get_data(double* fraction_done, Glib::ustring* message) const;
      void stop_work();
      bool has_stopped() const;

    private:
      // Synchronizes access to member data.
      mutable std::mutex m_Mutex;

      // Data used by both GUI thread and worker thread.
      bool m_shall_stop;
      bool m_has_stopped;
      double m_fraction_done;
      Glib::ustring m_message;
    };

    #endif // GTKMM_EXAMPLEWORKER_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow() :
      m_VBox(Gtk::Orientation::VERTICAL, 5),
      m_ButtonBox(Gtk::Orientation::HORIZONTAL),
      m_ButtonStart("Start work"),
      m_ButtonStop("Stop work"),
      m_ButtonQuit("_Quit", /* mnemonic= */ true),
      m_ProgressBar(),
      m_ScrolledWindow(),
      m_TextView(),
      m_Dispatcher(),
      m_Worker(),
      m_WorkerThread(nullptr)
    {
      set_title("Multi-threaded example");
      set_default_size(300, 300);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ProgressBar.
      m_VBox.append(m_ProgressBar);

      m_ProgressBar.set_text("Fraction done");
      m_ProgressBar.set_show_text();

      // Add the TextView, inside a ScrolledWindow.
      m_ScrolledWindow.set_child(m_TextView);

      // Only show the scrollbars when they are necessary.
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);

      m_TextView.set_editable(false);

      // Add the buttons to the ButtonBox.
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_ButtonStart);
      m_ButtonBox.append(m_ButtonStop);
      m_ButtonBox.append(m_ButtonQuit);
      m_ButtonBox.set_margin(5);
      m_ButtonBox.set_spacing(5);
      m_ButtonStart.set_hexpand(true);
      m_ButtonStart.set_halign(Gtk::Align::END);

      // Connect the signal handlers to the buttons.
      m_ButtonStart.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_start_button_clicked));
      m_ButtonStop.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_stop_button_clicked));
      m_ButtonQuit.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_quit_button_clicked));

      // Connect the handler to the dispatcher.
      m_Dispatcher.connect(sigc::mem_fun(*this, &ExampleWindow::on_notification_from_worker_thread));

      // Create a text buffer mark for use in update_widgets().
      auto buffer = m_TextView.get_buffer();
      buffer->create_mark("last_line", buffer->end(), /* left_gravity= */ true);

      update_start_stop_buttons();
    }

    void ExampleWindow::on_start_button_clicked()
    {
      if (m_WorkerThread)
      {
        std::cout << "Can't start a worker thread while another one is running." << std::endl;
      }
      else
      {
        // Start a new worker thread.
        m_WorkerThread = new std::thread(
          [this]
          {
            m_Worker.do_work(this);
          });
      }
      update_start_stop_buttons();
    }

    void ExampleWindow::on_stop_button_clicked()
    {
      if (!m_WorkerThread)
      {
        std::cout << "Can't stop a worker thread. None is running." << std::endl;
      }
      else
      {
       // Order the worker thread to stop.
        m_Worker.stop_work();
        m_ButtonStop.set_sensitive(false);
      }
    }

    void ExampleWindow::update_start_stop_buttons()
    {
      const bool thread_is_running = m_WorkerThread != nullptr;

      m_ButtonStart.set_sensitive(!thread_is_running);
      m_ButtonStop.set_sensitive(thread_is_running);
    }

    void ExampleWindow::update_widgets()
    {
      double fraction_done;
      Glib::ustring message_from_worker_thread;
      m_Worker.get_data(&fraction_done, &message_from_worker_thread);

      m_ProgressBar.set_fraction(fraction_done);

      if (message_from_worker_thread != m_TextView.get_buffer()->get_text())
      {
        auto buffer = m_TextView.get_buffer();
        buffer->set_text(message_from_worker_thread);

        // Scroll the last inserted line into view. That's somewhat complicated.
        auto iter = buffer->end();
        iter.set_line_offset(0); // Beginning of last line
        auto mark = buffer->get_mark("last_line");
        buffer->move_mark(mark, iter);
        m_TextView.scroll_to(mark);
        // TextView::scroll_to(iter) is not perfect.
        // We do need a TextMark to always get the last line into view.
      }
    }

    void ExampleWindow::on_quit_button_clicked()
    {
      if (m_WorkerThread)
      {
        // Order the worker thread to stop and wait for it to stop.
        m_Worker.stop_work();
        if (m_WorkerThread->joinable())
          m_WorkerThread->join();
      }
      close();
    }

    // notify() is called from ExampleWorker::do_work(). It is executed in the worker
    // thread. It triggers a call to on_notification_from_worker_thread(), which is
    // executed in the GUI thread.
    void ExampleWindow::notify()
    {
      m_Dispatcher.emit();
    }

    void ExampleWindow::on_notification_from_worker_thread()
    {
      if (m_WorkerThread && m_Worker.has_stopped())
      {
        // Work is done.
        if (m_WorkerThread->joinable())
          m_WorkerThread->join();
        delete m_WorkerThread;
        m_WorkerThread = nullptr;
        update_start_stop_buttons();
      }
      update_widgets();
    }

File: `exampleworker.cc` (For use with gtkmm 4)

    #include "exampleworker.h"
    #include "examplewindow.h"
    #include <sstream>
    #include <chrono>

    ExampleWorker::ExampleWorker() :
      m_Mutex(),
      m_shall_stop(false),
      m_has_stopped(false),
      m_fraction_done(0.0),
      m_message()
    {
    }

    // Accesses to these data are synchronized by a mutex.
    // Some microseconds can be saved by getting all data at once, instead of having
    // separate get_fraction_done() and get_message() methods.
    void ExampleWorker::get_data(double* fraction_done, Glib::ustring* message) const
    {
      std::lock_guard<std::mutex> lock(m_Mutex);

      if (fraction_done)
        *fraction_done = m_fraction_done;

      if (message)
        *message = m_message;
    }

    void ExampleWorker::stop_work()
    {
      std::lock_guard<std::mutex> lock(m_Mutex);
      m_shall_stop = true;
    }

    bool ExampleWorker::has_stopped() const
    {
      std::lock_guard<std::mutex> lock(m_Mutex);
      return m_has_stopped;
    }

    void ExampleWorker::do_work(ExampleWindow* caller)
    {
      {
        std::lock_guard<std::mutex> lock(m_Mutex);
        m_has_stopped = false;
        m_fraction_done = 0.0;
        m_message = "";
      } // The mutex is unlocked here by lock's destructor.

      // Simulate a long calculation.
      for (int i = 0; ; ++i) // do until break
      {
        std::this_thread::sleep_for(std::chrono::milliseconds(250));

        {
          std::lock_guard<std::mutex> lock(m_Mutex);

          m_fraction_done += 0.01;

          if (i % 4 == 3)
          {
            std::ostringstream ostr;
            ostr << (m_fraction_done * 100.0) << "% done\n";
            m_message += ostr.str();
          }

          if (m_fraction_done >= 1.0)
          {
            m_message += "Finished";
            break;
          }
          if (m_shall_stop)
          {
            m_message += "Stopped";
            break;
          }
        }

        caller->notify();
      }

      {
        std::lock_guard<std::mutex> lock(m_Mutex);
        m_shall_stop = false;
        m_has_stopped = true;
      }

      caller->notify();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

Using Glib::Dispatcher |  | Chapter 30. Recommended Techniques