更改gtkmm小部件

时间:2015-09-14 20:54:25

标签: c++ c++14 gtkmm

HY, 我有一个gtkmm应用程序,它执行一些异步网络请求,要求服务器提供gtk-widgets的其他属性。 这意味着,例如,应用程序应该能够更改窗口小部件的标签。

在这个例子中,我创建了一个基于Gtk :: ToggleButton的新小部件。

但我发现有时gtkmm应用程序崩溃了一个段错误。使用gdb进行debuging时,我总是得到我设置标签的行。

为了更好地理解,我创建了一个MWE,它在循环中进行标签更改,以模拟大量的异步调用:

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>

#include <iostream>
#include <thread>
#include <mutex>
#include <gtkmm/application.h>
#include <gtkmm/window.h>
#include <gtkmm/togglebutton.h>

class led_label_t : public Gtk::ToggleButton {
public:
    using value_list_t = std::vector<Glib::ustring>;
    using lock_t = std::lock_guard<std::mutex>;

    led_label_t(Glib::ustring label = "<no data>", bool mnemonic = false)
        : Gtk::ToggleButton(std::move(label), std::move(mnemonic)),
          _values{"SEL1", "SEL2"} {}

protected:
    virtual void on_toggled(void) override {
        std::cout << "Clicked Button." << std::endl;
        lock_t lock(_mtx);
        value_changed(_values[get_active()]);
    }

    virtual void value_changed(Glib::ustring& value) {
        std::string path;
        if (get_active()) {
            path =
                "/usr/share/icons/Adwaita/16x16/emblems/emblem-important.png";
        } else {
            path = "/usr/share/icons/Adwaita/16x16/emblems/emblem-default.png";
        }
        remove();  // remove previous label
        std::cout << "Changed Label of led_label: "
                  << ", value: " << value << std::endl;
        add_pixlabel(path, value);
    }

private:
    mutable std::mutex _mtx;
    value_list_t _values;
};

int main(void) {
    auto app = Gtk::Application::create();
    Gtk::Window window;
    window.set_default_size(200, 200);

    led_label_t inst{};
    inst.show();
    window.add(inst);

    auto f = [&inst, &window]() {
        using namespace std::chrono_literals;
        boost::asio::io_service io;
        {   //wait for startup
            boost::asio::steady_timer t{io, 100ms};
            t.wait();
        }

        bool toggle = true;
        for (auto i = 0; i < 2000; i++) {
            std::cout << "i=" << i << std::endl;
            //wait until next simulated button click
            boost::asio::steady_timer t{io, 1ms};
            t.wait();
            inst.set_active(toggle);
            toggle = !toggle;
        }
    };

    std::thread c1(f);
    std::thread w([&app, &window]() { app->run(window); });
    c1.join();
    window.hide();
    w.join();
    return EXIT_SUCCESS;
}

要编译此示例,我使用以下命令:

g++ main.cpp -o main `pkg-config --cflags --libs gtkmm-3.0` -Wall -pedantic -Wextra -Werror -Wcast-qual -Wcast-align -Wconversion -fdiagnostics-color=auto -g -O0 -std=c++14 -lboost_system -pthread

我正在使用GCC 4.9.2和libgtkmm-3.14(两者都是标准的debian jessie)

我得到的段错误如下:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffe7fff700 (LWP 7888)]
0x00007ffff6288743 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
(gdb) bt
#0  0x00007ffff6288743 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#1  0x00007ffff6288838 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#2  0x00007ffff6267ce9 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#3  0x00007ffff627241b in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#4  0x00007ffff63a1601 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#5  0x00007ffff63a154c in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#6  0x00007ffff63a26b8 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#7  0x00007ffff644d5ff in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#8  0x00007ffff644d9b7 in gtk_widget_realize ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#9  0x00007ffff644dbe8 in gtk_widget_map ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#10 0x00007ffff621c387 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#11 0x00007ffff626270f in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#12 0x00007ffff46bf474 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#13 0x00007ffff46d9087 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#14 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#15 0x00007ffff644db99 in gtk_widget_map ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#16 0x00007ffff64506d8 in gtk_widget_set_parent ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#17 0x00007ffff6217a9b in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#18 0x00007ffff79a44eb in Gtk::Container_Class::add_callback(_GtkContainer*, _GtkWidget*) () from /usr/lib/x86_64-linux-gnu/libgtkmm-3.0.so.1
#19 0x00007ffff46c253b in g_cclosure_marshal_VOID__OBJECTv ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#20 0x00007ffff46bf474 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#21 0x00007ffff46d9087 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#22 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#23 0x00007ffff6261aa5 in gtk_container_add ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#24 0x000000000040b0b5 in led_label_t::value_changed (this=0x7fffffffe2a0, 
    value=...) at main.cpp:38
#25 0x000000000040afb1 in led_label_t::on_toggled (this=0x7fffffffe2a0)
    at main.cpp:24
#26 0x00007ffff7a18af0 in Gtk::ToggleButton_Class::toggled_callback(_GtkToggleButton*) () from /usr/lib/x86_64-linux-gnu/libgtkmm-3.0.so.1
#27 0x00007ffff46bf245 in g_closure_invoke ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#28 0x00007ffff46d083b in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#29 0x00007ffff46d9778 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#30 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#31 0x00007ffff63ecb4d in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#32 0x00007ffff798a4a0 in Gtk::Button_Class::clicked_callback(_GtkButton*) ()
   from /usr/lib/x86_64-linux-gnu/libgtkmm-3.0.so.1
#33 0x00007ffff46bf474 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#34 0x00007ffff46d9087 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#35 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#36 0x00007ffff63ec936 in gtk_toggle_button_set_active ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#37 0x0000000000405e12 in <lambda()>::operator()(void) const (
    __closure=0x74f4f8) at main.cpp:73
#38 0x000000000040811a in std::_Bind_simple<main()::<lambda()>()>::_M_invoke<>(std::_Index_tuple<>) (this=0x74f4f8) at /usr/include/c++/4.9/functional:1700
#39 0x0000000000407fa9 in std::_Bind_simple<main()::<lambda()>()>::operator()(void) (this=0x74f4f8) at /usr/include/c++/4.9/functional:1688
#40 0x0000000000407e9e in std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()> >::_M_run(void) (this=0x74f4e0) at /usr/include/c++/4.9/thread:115
#41 0x00007ffff3f47970 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#42 0x00007ffff37650a4 in start_thread (arg=0x7fffe7fff700)
    at pthread_create.c:309
#43 0x00007ffff349a04d in clone ()
    at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

可能有趣的一行是

#24: 0x000000000040b0b5 in led_label_t::value_changed (this=0x7fffffffe2a0, 
    value=...) at main.cpp:38)

这是add_pixlabel(路径,值)的行;被称为。

我在这里做错了什么?

注意: 我发现,这个段错误并不总是在我的台式机上每10次调用就会收到一次错误。 (英特尔i7-3xxx) 在我的笔记本电脑上,我几乎每次通话都会收到错误(Intel i5-3xxx)

2 个答案:

答案 0 :(得分:2)

现在我找到了一个解决方案,基于@ user4581301的答案。他是对的,gtkmm不支持多线程。 (更准确地说,libsigc ++和sigc :: trackable不是线程安全的)

  

但是,在编写基于gtkmm的程序时需要小心   由libsigc ++引起的多个执行线程,   特别是sigc :: trackable,不是线程安全的。

     

来自gtkmm documentation

因此我使用了Glib::Dispatcher来在窗口的gtkmm-Main-Loop的上下文中执行set_label() - 方法。

这是代码,在我的机器上不再出现段错误(即使有很多重试)

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>

#include <cassert>
#include <iostream>
#include <thread>
#include <mutex>
#include <gtkmm/application.h>
#include <gtkmm/window.h>
#include <gtkmm/togglebutton.h>
#include <glibmm/dispatcher.h>

#define LOG()                                                              \
    std::cout << (std::chrono::system_clock::now() - start).count() << " " \
              << std::this_thread::get_id() << ": "

auto start = std::chrono::system_clock::now();

class led_label_t : public Gtk::ToggleButton {
public:
    using value_list_t = std::vector<Glib::ustring>;
    using lock_t = std::lock_guard<std::mutex>;
    using action_queue_t = std::vector<Glib::ustring>;

    led_label_t(Glib::ustring label = "<no data>", bool mnemonic = false)
        : Gtk::ToggleButton(std::move(label), std::move(mnemonic)),
          _values{"SEL1", "SEL2"} {}

    void set_dispatcher(Glib::Dispatcher* dp) {
        _dp = dp;
        _dp->connect([this](void) { dispatcher_task(); });
    }

protected:
    virtual void on_toggled(void) override {
        LOG() << "Clicked Button." << std::endl;
        {
            lock_t lock(_action_mtx);
            auto value = _values[get_active()];
            _action_queue.push_back({value});
            LOG() << "Added label into queue " << value << std::endl;
            if (_action_queue.size() > 1) {
                return;
            }
        }
        _dp->emit();
    }

    void dispatcher_task(void) {
        Glib::ustring label;
        for (;;) {
            {
                lock_t lock(_action_mtx);
                if (_action_queue.size() == 0) {
                    return;
                }
                label = *_action_queue.begin();
                _action_queue.erase(_action_queue.begin());
            }
            set_label(label);
            LOG() << "Set the label " << label << std::endl;
        }
    }

private:
    mutable std::mutex _action_mtx;
    action_queue_t _action_queue;

    value_list_t _values;
    Glib::Dispatcher* _dp;
};

int main(void) {
    auto app = Gtk::Application::create();
    Gtk::Window window;
    window.set_default_size(200, 200);

    led_label_t inst{};
    inst.show();
    window.add(inst);

    auto f = [&inst, &window]() {
        using namespace std::chrono_literals;
        boost::asio::io_service io;
        {  // wait for startup
            boost::asio::steady_timer t{io, 100ms};
            t.wait();
        }

        bool toggle = true;
        for (auto i = 0; i < 200000; i++) {
            // wait until next simulated button click
            boost::asio::steady_timer t{io, 250us};
            t.wait();
            LOG() << "i=" << i << std::endl;
            inst.set_active(toggle);
            toggle = !toggle;
            LOG() << "finished" << std::endl;
        }
    };

    std::thread c1(f);
    std::thread w([&app, &window, &inst]() {
        Glib::Dispatcher dp;
        inst.set_dispatcher(&dp);
        app->run(window);
    });
    c1.join();
    window.hide();
    w.join();
    return EXIT_SUCCESS;
}

答案 1 :(得分:1)

从多个线程访问和更改UI组件总是很棘手。用户界面需要快速响应用户输入,因此无法完成后台任务。因此,UI组件很少受互斥或其他同步保护。你写,它发生了。除非其他东西妨碍了。

如果你从两个线程写...哎呀。

当另一个线程读取时,你已经写了一半......哎呀。

比如说,当触发屏幕刷新时,线程4是将新字符串写入标签的一部分。如果标签的后端是一个c风格的字符串,则终止NULL可能已被覆盖,标签写入结束运行到坏RAM。

各种各样的事情都可能出错,有些可能会存活,或者更糟糕的是看起来像。您最好在一个线程中拥有所有UI管理,并让其他线程将更新排队到UI线程。首先查看Model View Controller,然后根据需要尝试相关模式。