示例
这是一个具有两个线程的示例程序,一个与所有gtkmm程序一样的GUI线程,和一个工作线程。当你按下Start work按钮时将创建工作线程,当工作完成或你按下Stop work按钮或是你按下Quit按钮时该线程将被删除。
Glib::Dispatcher被用于工作线程向GUI线程发送通知。ExampleWorker包含两个线程都可以访问的数据。该数据被一个std::mutex保护。只有GUI线程对GUI进行更新。
编译和链接多线程程序可能需要特殊的编译和链接参数。如果你使用g++编译器你需要添加-pthread选项。其他编译器可能会要求使用其他选项。如果你使用meson构建程序,如果你在构建脚本中添加了dependency('threads'),它会帮你处理多线程的复杂性。
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: 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); }
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(); } 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(); }
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(); }