前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >boost的信号槽原理和实践

boost的信号槽原理和实践

原创
作者头像
mariolu
修改2024-01-26 09:17:40
16300
代码可运行
修改2024-01-26 09:17:40
举报
运行总次数:0
代码可运行

一、signal/slot是什么

Signals代表绑定在目标的回调callbacks ,有点类似于订阅/发布系统的发布者publishers 。Signals 连接到 slots, slots是回调函数的接受者callback receivers 类似于订阅者,当signal被call的时候也称为"emitted."

说到signal/slot,你可能首先想到的是QT。 没错,Qt的MOC,实现不是为了高性能目的,而是为了内存记录。MOC 产生的data很小(包含信号,properties,text enum表达式)。Qt在C++11出现以前就有了,因为他们不依赖于任何class函数指针,所以他们很方便移植。

还有一个有趣的特性是QObject有一个固定的空间来实现signals和slots,所以即使object有很多slots,并且没有连接到这些slot,依然没有额外的开销,得益于slot不是class member。

但是你用着Qt,其实你还要考虑QObject和thread affinity。如果不需要每秒调用百万个call,不太需要关心底层设计机制。Qt的确说好选择。但是你需要知道的是Qt Siganl/Slot使用的是QVariant折叠传输,展开解析。所以触发signal必然会有开销,而且如果有多个signal连接到一个QObject,开销也增长。

性能考虑最好的办法是也许就是使用裸函数指针。

还比如说signal/slot机制需要你去思考怎么track subscribers,intrusive list,单线程list,subscribe结束,这些都需要cost。

怎么实现slot/signal在实时系统?(在实时forloop中怎么安排每项事情)

而boost signal2提供了这么一种高性能的底层库。这里我觉得在一些场景下,boost signal2也是不错的选择。

二、boost的设计原理

2.1 boost signal2的一些设计亮点

  • “类型擦除”,即通过使用动态分派接口消除静态类型信息,在 Boost.Signals 库中广泛使用,以减少模板实例化生成的代码量。
  • 每个信号必须管理slot列表及其关联连接,以及从组标识符到其关联连接的映射。然而,为每个标记类型实例化此映射,会增加编译时间开销和空间开销。为了对抗这种所谓的“模板膨胀”,使用 Boost.Function 和 Boost.Any 来存储未知类型和操作。然后,用于处理槽列表以及从槽标识符到连接的映射的所有代码都被分解到signal_base 专门处理anyfunction对象的类中,使用众所周知的 pimpl 惯用法隐藏实际实现。实际的signalN类模板仅处理根据参数数量而变化的代码,或者本质上依赖于模板的代码(例如连接)。
  • 通过connections管理signal/slot。在任何一方销毁的时候,断掉这个connection. connection解放了用户需要去关注signal/slot生命周期的烦恼。
  • connection管理职责包括查询connected/disconnected,手动断连 和因为析构函数的自动断联。断开连接需要线性查找对应的slot/signal,复杂度为O(n)
  • signals 如果连接了多个 slots, 有个问题需要考虑signal和slot的返回值连接。Boost.Signals2 可以指定多个返回值绑定
  • Signals2使用 "pull" 模式,而不是 "push" 模式. "pull" 模式可以保存combiner's state在栈上和, 新数据来的时候,过来一下数据。相反“push" 模式需要combiner保存各个状态,如果slots过多,会是不小的开销。
  • signal2的接口由signal的last_vale改为optional_last_value。这是因为last_value要求需要至少1个 slot 连接到signal。在多线程环境下signal唤起跟连接和断开可能同时发生,因此last_value改为optional_last_value, 这就没要求至少要有1个slot当signal唤醒时。如果没有可用的slot,那么返回空的boost::optional。

connection的关键api

代码语言:javascript
复制
// In header: <boost/signals/connection.hpp>


class connection {
public:
  // construct/copy/destruct
  connection();
  connection(const connection&);
  connection& operator=(const connection&);

  // connection management
  void disconnect() const;
  bool connected() const;

  // blocking
  void block(bool = true);
  void unblock();
  bool blocked() const;

  // modifiers
  void swap(const connection&);

  // comparisons
  bool operator==(const connection&) const;
  bool operator<(const connection&) const;
};

// specialized algorithms
void swap(connection&, connection&);

2.2 线程安全:

Signal2是个线程安全. api也是thread-safety, 能做到这样是因为自动化connection管理的内部设计机制。

2.2.1 用户层面Connection 管理

signal2使用了boost::shared_ptr管理对象的生命周期。原来的Boost.Signals 使用boost::signals::trackable派生类。

由于boost::signals::trackable派生类的析构函数顺序先于base类的析构函数。signal和object需要先进行断开connection。因此有可能存在这么一个情况connection还在,但是connection管理的object已不在了。在多线程环境中,如果一个object在一个线程被析构了,另一个线程的signal会call到摧毁的object。

signal2使用了shared_pt机制来解决这个问题。Slots持有每个对象的weak_ptr. 当这个weak_ptr无效的时候,Connections到 slot 被断开。signals持有所有slot的shared_ptr. 这 确保了调用期间没有对象被销毁 。 同时使用shared_ptr和weak_ptr可以模版各种类,相比继承boost::signals::trackable代码实现更具有非侵入性

三、实践

这里介绍了一个简单的入门例子。当然boost的能力不止这些。

代码语言:javascript
复制
#include <iostream>
#include <boost/signals2/signal.hpp>

//[ hello_world_def_code_snippet
struct HelloWorld
{
  void operator()() const
  {
    std::cout << "Hello, World!" << std::endl;
  }
};
//]

int main()
{
//[ hello_world_single_code_snippet
  // Signal with no arguments and a void return value
  boost::signals2::signal<void ()> sig;

  // Connect a HelloWorld slot
  HelloWorld hello;
  sig.connect(hello);

  // Call all of the slots
  sig();
//]

  return 0;
}

再来举一个多线程的例子

编译

代码语言:bash
复制
g++ signal2_multithread.cc -lpthread

代码语言:cpp
复制
#include <boost/signals2.hpp>
#include <thread>
#include <mutex>
#include <iostream>


boost::signals2::signal<void(int)> s; // 定义了signal s,接受了一个int类型的输入
std::mutex m;

void loop()
{
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    s(i);  // 发起一次signal
  }
}

int main()
{
  s.connect([](int i){  //定义了slot函数,signal通过connect连接slot
    std::lock_guard<std::mutex> lock{m};
    std::cout << i << '\n';
  });
  std::thread t1{loop};  // 线程t1启动
  std::thread t2{loop};  // 线程t2启动
  t1.join();
  t2.join();
}       

屏幕打印如下:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、signal/slot是什么
  • 二、boost的设计原理
    • 2.1 boost signal2的一些设计亮点
      • 2.2 线程安全:
        • 2.2.1 用户层面Connection 管理
    • 三、实践
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档