Boost库学习笔记(4)—— Signals2

Boost.Signals2是一个头文件库,提供信号和槽的管理,支持多线程环境下的安全使用。信号与槽之间的连接自动管理,避免资源泄露。信号发射时,槽的执行是线程安全的,通过互斥锁保护,防止死锁。连接和断开操作在槽执行时可能发生,但不影响线程安全性。slots和connections类提供了线程安全的方法,但需注意非线程安全的赋值和交换操作。自定义组合器需确保线程安全,以防止并发调用时出现问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

        Boost.Signals2库一个信号和槽管理系统的实现。信号表示带有多个目标的回调,在类似的系统中也成为发布者或者事件。信号被连接到一些槽上,这些槽是回调接收者(也成为事件目标或者订阅方),当信号被“发射”时调用。
        信号和槽是受管理的,在这种情况下,信号和槽(或者更确切的说,作为槽的一部分出现的对象)可以跟踪连接,并且能够在其中一方被销毁时自动断开信号/槽连接。这使得用户能够创建信号/槽连接,不需要花费大力气来管理这些连接的生命周期,这些生命周期与所有涉及对象的生命周期有关。
        当信号连接到多个槽时,有一个关于槽返回值和信号返回值之间关系的问题。Boost.Signals2允许用户指定组合多个返回值的方式。
        不同于初版的Boost.Signals库,Boost.Signals2当前只有头文件,要使用库,只需要代码中包含头文件:#include <boost/signals2.hpp>

二、线程安全

        信号库可以在多线程环境中安全的使用。这主要是通过对原始库的两个改变来实现的:一个是引入了新的依赖于shared_ptrweak_ptr的自动连接管理方案,第二个变化时再signal类中引入了一个互斥模板类型参数。

1、信号和组合器

        每个信号对象默认构造一个互斥对象来保护其内部状态。此外,每次将一个新槽连接到信号时,都会创建一个互斥锁,以保护相关联的信号槽连接。
        当信号的任意方法被调用时,信号的互斥锁都会被自动锁定。互斥锁通常会一直保持到方法调用结束,但是这个规则有一个主要的例外。当通过调用signal::operator()调用一个信号时,调用首先获得该信号的互斥锁。然后它获得信号的槽列表的句柄,以及组合器的句柄。接下来,在调用组合器遍历槽列表之前,释放信号的互斥锁。因此,当槽执行时,信号不持有互斥锁。这种设计选择,使得槽中运行的代码不可能由于Boost.Signals2库内部使用任意互斥而导致死锁。它还可以防止槽由于意外而导致对库内部互斥递归加锁的情况。因此,如果在多个线程并发地调用一个信号,则可能并发地调用信号的组合器,从而使槽并发地执行。
        在组合器调用期间,将执行以下步骤,以便在遍历信号的槽列表时查找下一个可调用槽。

  • 锁住与槽连接相关联的互斥量。
  • 所有跟踪到的与槽相关联的weak_ptr都被复制到临时shared_ptr中,该临时shared_ptr将一直保持活动状态,直到对槽的调用完成。如果由于任何一个weak_ptr过期而导致此操作失败,则连接将自动断开。因此,如果某个槽所跟踪的任何weak_ptr已经过期,那么该槽将永远不会运行,并且在槽运行时,该槽所跟踪的任何weak_ptr都不会过期。
  • 检查槽的连接是否被阻塞或断开,然后解锁连接的互斥量。如果连接被阻塞或断开,将从槽列表中的下一个槽重新开始。否则,我们将执行槽,当组合器调用迭代器解引用下一个槽时(除非组合器递增迭代器而不取消引用)。

        注意,如果断开连接和信号是并发调用的,由于在执行关联的槽之前解锁了连接的互斥量,所以有可能槽在被connection::disconnect()断开后仍然在执行。
        你可能已经注意到,在信号调用过程中,调用只获得信号槽列表句柄和组合器的句柄,同时持有信号的互斥锁。因此,并发信号调用可能仍然会并发地访问相同的槽列表和组合器。那么,如果槽列表被修改了,例如通过连接一个新的槽,而信号调用同时进行,会发生什么呢?如果槽列表已经在使用中,信号在修改槽列表之前执行深拷贝。因此,并发信号调用将继续使用旧的未修改的槽列表,不受对槽列表新创建的深拷贝所做修改的干扰。新的信号调用将接收到新创建的深拷贝的槽列表句柄,旧的槽列表一旦不再使用就会被销毁。同样地,如果在一个信号调用并发运行时,用signal::set_combiner()改变一个信号的组合器,则并发信号调用将继续不受干扰地使用旧的组合器,而新的信号调用将收到一个新组合器的句柄。
        由于并发信号调用使用相同的组合器对象,这意味着需要确保所编写的任何自定义组合器都是线程安全的。因此,如果您的组合器维护的状态在组合器被调用时被修改,就可能需要使用互斥锁来保护该状态。要注意的是,如果在组合器中有一个互斥锁,而调用迭代器解引用槽时,如果任意的槽导致额外的互斥锁发生,就会可能有死锁和递归锁的风险。避免这些危险的一种方法是组合器在调用迭代器解引用槽之前释放所有锁。Boost.Signals2库提供的组合器类时线程安全的,因为它们调用时不需要维护任何状态。
        假设写了一个槽,该槽将另一个槽连接到调用信号。新连接的槽是否会在创建新连接的相同信号调用期间运行?答案是否定的。连接新槽会修改信号的槽列表,如前所述,正在进行的信号调用不会看到对槽列表的任何修改。
        假设写了一个槽,它断开了另一个槽与调用信号的连接。如果断开连接的槽出现在槽列表中的时间比断开连接的槽的时间晚,是否会阻止该槽在同一信号调用期间运行?这一次,答案是肯定的。即使断开连接的槽仍然存在于信号的槽列表中,每个槽都会被检查,看它是否在执行之前立即被断开或阻塞(或者不执行,视情况而定),正如上面所描述的那样。

2、连接和其他类

        类signals2::connection的方法是线程安全的,除了赋值和交换之外。这是通过锁定与对象的底层信号槽连接相关联的互斥锁来实现的。分配和交换不是线程安全的,因为互斥锁保护的是signals2::connection对象引用的底层连接,而不是signals2::connection对象本身。也就是说,signals2::connection对象可能有多个拷贝,所有这些拷贝都引用相同的底层连接。每个signals2::connection对象都没有互斥锁,只有一个互斥锁保护它们引用的底层连接。
        类shared_connection_block从保护阻塞和非阻塞的底层连接互斥锁中获得线程安全。用于跟中有多少shared_connection_block对象在其底层连接上是断言块的内部引用计数也是线程安全的(该实现依赖于shared_ptr进行引用计数)。但是,单个的shared_connection_block对象不应该被多个线程并发地访问。只要两个线程都有自己的shared_connection_block对象,那么线程就可以安全地使用它们,即使两个shared_connection_block对象都是拷贝并引用相同的底层连接。
        类signals2::slot没有内建的内部互斥锁。它用于创建槽对象,然后在单线程中连接到信号。一旦它们被拷贝到信号的槽列表中,它们就会受到与每个信号槽连接相关联的互斥锁的保护。
        类signals2::trackable不提供线程安全的自动连接管理。特别是,如果可跟踪的派生对象在调用信号所在的不同线程中被销毁时,它将有可能使得信号调用到部分破坏的对象。signals2::trackable只是为了方便移植Boost.Signals的单线程代码到Boost.Signals2。

### Boost C++ Libraries概述 Boost是一套广泛使用的C++集合,旨在提供高质量的通用组件。这些经过严格的同行评审过程,并遵循现代C++编程实践[^1]。 #### 主要特点 - **跨平台支持**:能够在多种操作系统上运行,包括Windows、Linux和macOS。 - **标准化影响**:许多Boost功能最终被纳入到ISO标准中,成为正式的C++特性的一部分。 - **丰富的工具集**:涵盖了从容器、算法、内存管理到线程处理等多个方面的需求。 #### 安装方法 安装Boost通常有两种方式: ##### 使用包管理器 对于大多数主流的操作系统而言,可以直接通过系统的软件包管理系统来获取预编译版本的Boost: ```bash sudo apt-get install libboost-all-dev # Ubuntu/Debian brew install boost # macOS Homebrew ``` ##### 手动构建 如果需要特定配置或最新版,则可以从源码自行编译: ```bash ./bootstrap.sh --prefix=/usr/local # 配置安装路径 ./b2 # 编译项目 sudo ./b2 install # 安装文件至指定位置 ``` #### 基本使用案例 下面展示了一个简单的例子,说明如何利用`boost::asio`来进行网络通信操作: ```cpp #include <iostream> #include <boost/asio.hpp> int main() { try { boost::asio::io_context io; boost::asio::ip::tcp::socket socket(io); // 这里可以加入更多代码实现具体的客户端连接逻辑 std::cout << "Socket created successfully." << std::endl; } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值