【问题标题】:sigslot signals across threads跨线程的 sigslot 信号
【发布时间】:2016-08-05 13:07:51
【问题描述】:

我正在使用 sigslot 库来触发函数中的信号。该函数使用 QtConcurrent::run 在线程中运行,信号在主线程中连接。 它按预期工作,除了信号连接不是每次都工作(假设大约 25% 失败)。

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

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

任何提示将不胜感激。

更新:出于某种原因,将 sigslot::single_threaded 用作绝望的尝试似乎会产生更好的结果。

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
}

【问题讨论】:

  • 很抱歉没有制作 MWE,我确实在寻找全局答案而不是代码调试(比如“Qt 和 sigslot 不能一起工作”)。由于行为不稳定且上下文非常复杂,我认为不值得举个例子
  • 最重要的缺失部分是:没有“the”sigslot 库。这是一个废弃的项目,可以从各种来源进行多个,呃,分解阶段。请添加指向您正在使用的版本的链接。
  • 好吧,我不知道这个库。我开始使用是因为我发现只有好的反馈。我正在使用这个版本:sourceforge.net/p/sigslot/patches/3
  • 除非所有的插槽都是线程安全的,否则这不可能奏效。具体来说,std::cout 不是线程安全的,因此从多个线程调用它已经是未定义的行为。
  • 请注意,您拥有的补丁不是必需的。 QT_NO_KEYWORDS 是您需要添加到项目的DEFINES 的宏,它可以修复它。您还使用了旧版本的 Visual Studio,这是唯一采用原始 sigslot 代码并且不会抱怨的编译器。原始 sigslot 不是有效的 C++。我将“很快”提供更新@@github.com/KubaO/sigslot

标签: c++ multithreading qt signals-slots


【解决方案1】:

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

我强烈建议您直接使用 Qt 或其他信号槽库。

唉,你的方法行不通:sigslot library 不知道 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_ 前缀代表线程安全/thunk。

【讨论】:

  • 感谢您的回答。 Qt 连接现在看起来没问题。但你是对的,我没有为插槽指定线程安全。问题更多与 sigslot 连接有关,该信号在 Qt 线程中触发但在主线程中连接(与 sigslot 插槽一起)。但也许我没有正确理解你的答案。
  • 再一次,不管发生在哪里,只要线程不同,sigslot 就不支持它。仔细阅读文档:sigslot 不知道(也不知道!)关于各种对象的位置。 QObject::thread() 是有充分理由的。没有这样的机制,线程安全的信号槽系统是不可能实现的。
  • 在 MWE 中使用 boost 并没有给出更好的结果。您提出的使用 QObject 的示例旨在替代 sigslot 或 boost 对吗?我真的想避免在 QtConcurrent::run 中使用的计算库中使用 Qt,如果在没有 GUI 的情况下使用库,它就太重了。无论如何,这很奇怪,现在在我的代码中,事情似乎工作正常(使用 sigslot),但考虑到你所说的,我不自信。软件应该是跨平台的,我担心将来会出现一些奇怪的行为
  • 不,这个例子不是替代品,它是让任何其他信号槽库以线程安全的方式访问 QObject 方法的要求,假设所有带槽的对象都是对象。如果你的带槽对象不是 QObjects,你需要让它们的方法线程安全或者重新实现一块核心 Qt。
  • 回顾一下:您关心的是连接的目标,即带有插槽的类。如果它是一个 QObject,那么它有一个已知的线程上下文,你应该从 sigslot 库中直接调用 thunk 信号,not 插槽。如果连接目标不是 QObject,则需要使那里的所有插槽都是线程安全的,或者必须在目标对象的线程中运行(第 3 方)事件循环,接收带有函子的事件在那里,并执行它们。
猜你喜欢
  • 2014-12-18
  • 2017-05-10
  • 2015-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-13
  • 2010-10-12
  • 1970-01-01
相关资源
最近更新 更多