<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Example</title> <link rel="stylesheet" type="text/css" href="style.css"> <meta name="generator" content="DocBook XSL Stylesheets V1.79.1"> <link rel="home" href="index.html" title="Programming with gtkmm 3"> <link rel="up" href="chapter-multi-threaded-programs.html" title="Chapter 29. Multi-threaded programs"> <link rel="prev" href="sec-using-glib-dispatcher.html" title="Using Glib::Dispatcher"> <link rel="next" href="chapter-recommended-techniques.html" title="Chapter 30. Recommended Techniques"> </head> <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> <div class="navheader"> <table width="100%" summary="Navigation header"> <tr><th colspan="3" align="center">Example</th></tr> <tr> <td width="20%" align="left"> <a accesskey="p" href="sec-using-glib-dispatcher.html"><img src="icons/prev.png" alt="Prev"></a> </td> <th width="60%" align="center">Chapter 29. Multi-threaded programs</th> <td width="20%" align="right"> <a accesskey="n" href="chapter-recommended-techniques.html"><img src="icons/next.png" alt="Next"></a> </td> </tr> </table> <hr> </div> <div class="sect1"> <div class="titlepage"><div><div><h2 class="title" style="clear: both"> <a name="sec-multithread-example"></a>Example</h2></div></div></div> <p> This is an example program with two threads, one GUI thread, like in all <span class="application">gtkmm</span> programs, and one worker thread. The worker thread is created when you press the <code class="literal">Start work</code> button. It is deleted when the work is finished, when you press the <code class="literal">Stop work</code> button, or when you press the <code class="literal">Quit</code> button. </p> <p> A <code class="classname">Glib::Dispatcher</code> is used for sending notifications from the worker thread to the GUI thread. The <code class="classname">ExampleWorker</code> class contains data which is accessed by both threads. This data is protected by a <code class="classname">std::mutex</code>. Only the GUI thread updates the GUI. </p> <div class="figure"> <a name="figure-multithread"></a><p class="title"><b>Figure 29.1. Multi-Threaded Program</b></p> <div class="figure-contents"><div class="screenshot"><div><img src="figures/multithread.png" alt="Multi-Threaded Program"></div></div></div> </div> <br class="figure-break"><p><a class="ulink" href="http://git.gnome.org/browse/gtkmm-documentation/tree/examples/book/multithread?h=gtkmm-3-24" target="_top">Source Code</a></p> <p>File: <code class="filename">examplewindow.h</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #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::ButtonBox 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 </pre> <p>File: <code class="filename">exampleworker.h</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #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 </pre> <p>File: <code class="filename">main.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "examplewindow.h" #include <gtkmm/application.h> int main (int argc, char* argv[]) { auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example"); ExampleWindow window; //Shows the window and returns when it is closed. return app->run(window); } </pre> <p>File: <code class="filename">exampleworker.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #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(); } </pre> <p>File: <code class="filename">examplewindow.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #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_border_width(5); set_default_size(300, 300); add(m_VBox); // Add the ProgressBar. m_VBox.pack_start(m_ProgressBar, Gtk::PACK_SHRINK); m_ProgressBar.set_text("Fraction done"); m_ProgressBar.set_show_text(); // Add the TextView, inside a ScrolledWindow. m_ScrolledWindow.add(m_TextView); // Only show the scrollbars when they are necessary. m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); m_VBox.pack_start(m_ScrolledWindow); m_TextView.set_editable(false); // Add the buttons to the ButtonBox. m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK); m_ButtonBox.pack_start(m_ButtonStart, Gtk::PACK_SHRINK); m_ButtonBox.pack_start(m_ButtonStop, Gtk::PACK_SHRINK); m_ButtonBox.pack_start(m_ButtonQuit, Gtk::PACK_SHRINK); m_ButtonBox.set_border_width(5); m_ButtonBox.set_spacing(5); m_ButtonBox.set_layout(Gtk::BUTTONBOX_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(); show_all_children(); } 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. Gtk::TextIter 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(); } hide(); } // 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(); } </pre> </div> <div class="navfooter"> <hr> <table width="100%" summary="Navigation footer"> <tr> <td width="40%" align="left"> <a accesskey="p" href="sec-using-glib-dispatcher.html"><img src="icons/prev.png" alt="Prev"></a> </td> <td width="20%" align="center"><a accesskey="u" href="chapter-multi-threaded-programs.html"><img src="icons/up.png" alt="Up"></a></td> <td width="40%" align="right"> <a accesskey="n" href="chapter-recommended-techniques.html"><img src="icons/next.png" alt="Next"></a> </td> </tr> <tr> <td width="40%" align="left" valign="top">Using Glib::Dispatcher </td> <td width="20%" align="center"><a accesskey="h" href="index.html"><img src="icons/home.png" alt="Home"></a></td> <td width="40%" align="right" valign="top"> Chapter 30. Recommended Techniques</td> </tr> </table> </div> </body> </html>