跨线程的sigslot信号

sigslot signals across threads

本文关键字:信号 sigslot 线程      更新时间:2023-10-16

我使用sigslot库来触发函数中的信号。此函数使用QtConcurrent::run在线程中运行,信号在主线程中连接。除了信号连接不是每次都能工作(比如说大约25%的故障(之外,它的工作效果与预期的一样好。

这种不稳定的行为是有问题的,我找不到解决方案。sigslot库中的信号根据多线程上下文有不同的选项,但它们都不能解决问题。

在尝试boost之前,我真的很想找到一个保持使用sigslot的解决方案,因为它是一个非常简单的库,我只需要在这部分代码中基本使用信号和插槽。我不想使用Qt,因为我更喜欢让代码的同一部分没有Qt。

任何提示都将不胜感激。

更新:由于某种原因,使用sigslot::single_thread作为绝望的尝试似乎会带来更好的结果。

signal1<int, single_threaded> Sig1;

我并不是说它解决了问题,因为它对我来说没有意义。正如文档中所解释的:单线程在单线程模式下,库不会尝试保护其内部数据结构跨线程。因此,至关重要的是,所有对构造函数、析构函数和信号的调用必须存在于单个线程中

更新2:

这是MWE。但结果是随机的。有时它完全有效,有时不是全部。我知道这听起来很奇怪,但这就是问题所在。我也尝试了boost::signals2而不是sigslot,但结果完全相同。在boost::signals2::mutex::lock((中有一个可执行的错误访问

class A {
    public :
    A() {}
    ~A() {}
    sigslot::signal1<int, sigslot::multi_threaded_local> sigslot_signal;
    boost::signals2::signal<void (int)> boost_signal;
    void func_sigslot() {
        for (int i=0;i<4;i++) {
            sigslot_signal.emit_signal(i);
        }
    }
    void func_boost() {
        for (int i=0;i<4;i++) {
            boost_signal(i);
        }
    }
};
class B : public sigslot::has_slots<sigslot::multi_threaded_local> {
    public :
    B() {}
    ~B() {}
    void test(int i) {
        std::cout << "signal triggered, i=" << i << std::endl;
    }
};
void main() {
    A a;
    B b;
    a.sigslot_signal.connect_slot(&b, &B::test);
    a.boost_signal.connect(boost::bind(&B::test, &b, _1));
    QtConcurrent::run(&a, &A::func_sigslot);//->crashes when signal emitted
    QtConcurrent::run(&a, &A::func_boost);//->crashes when signal emitted
    boost::thread t1(boost::bind(&A::func, &a));
    t1.join();//works fine
}

Sarah Thompson的sigslot库(如果您使用的是它的话(很旧,不受支持,而且看起来很bug。没有任何类型的测试工具。原始源代码不是在现代编译器下编译的。由于MSVC以前将模板视为令牌列表,因此隐藏了一些拼写错误:显然,部分代码从未使用过!

我强烈建议您只需使用Qt或其他信号槽库

遗憾的是,您的方法不起作用:sigslot库不知道Qt的线程上下文,也不与Qt的事件循环集成。插槽是从错误的线程上下文调用的。由于您可能没有编写线程安全的插槽,因此它们不会做正确的事情,而且似乎不起作用。

sigslot库的线程支持只保护库自己的数据,而不是您的数据。设置多线程策略只会影响库的数据。这与Qt形成了鲜明对比,在Qt中,每个QObject的线程上下文都是已知的,并使信号槽系统能够安全地工作。

为了让它发挥作用,您需要在您正在调用其插槽的所有QObject中公开一个线程安全接口。这可以简单到:

class Class : public QObject {
  Q_OBJECT
public:
  Class() {
    // This could be automated via QMetaObject and connect overload
    // taking QMetaMethod
    connect(this, &Class::t_slot, this, &Class::slot);
  }
  Q_SIGNAL void t_slot();
  Q_SLOT slot() { ... }
}

不连接到slot(),而是连接到t_slot(),其中t_前缀代表线程安全/tunk。