boost::signals::signal的用法

本文通过两个实例演示如何使用Boost.Signals实现观察者模式。第一个实例模拟了门铃响动引起的不同反应,第二个实例则构建了一个简易的停车场监视系统,展示了Boost.Signals在降低代码耦合度方面的优势。

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

吃力的讲完boost::signals的ppt,然后接着就是做练习题。通过讲ppt,发现有一句话说的真好:你自己知道是一回事,你能给别人讲明白又是另外一回事。真的有些东西你自己理解,但是用语言去很难表达,是不是用语言难以表达暴露出自己对所理解的东西不是真正的理解?。。。。orz,不过讲完ppt之后,真的对boost::singals的理解又加深一层。好了废话不多说了,现在来看两个习题(ppt的内容基本上是boost官方网站介绍boost::singlas用法的章节,这里不贴了):

第一题:

题目1:实现一个日常生活里出现的情景

描述:

     客人按门铃,门铃响,护士开门,婴儿惊醒后苦恼。

要求:

     有至少两个护士和两个婴儿,并且某护士去开门是一个随机事件(随机数),只有当满足某个条件时才去开门,比如随机数randX满足500<randX%1000<999的时候才去开门,当然如果有一个护士去开门,剩下的护士就不用去开门了!门铃响,某个婴儿被惊醒也是一个随机事件,当满足一定条件时,这个婴儿醒来就哭叫,没醒的继续睡觉_~~_。

这个题就是想实现一个简单的观察这模式,如果不用boost::signals来实现,我们就得按照观察模式来写,首先肯定有一个观察者的基类,然后是n多派生观察者;其次要定义一个被观察者基类,然后就是m多被观察者,下面是一个实现:

<span style="font-size:14px;">namespace TestRing 
{
	//观察者基类
	class CPerson
	{
	public:
		CPerson(const std::string& vName) : m_Name(vName) {}
		virtual ~CPerson()                {}

		//观察者收到通知(触发信号)的动作
		virtual void act()                
		{
			std::cout<<"no action \n";
		}
		const std::string getName() const {return m_Name;}

	private:
		std::string m_Name;
	};

	//一个派生子类,实际观察者
	class CNurse : public CPerson
	{
	public:
		CNurse(const std::string& vName) : CPerson(vName) {}

		virtual void act() {std::cout<<"Nurse " <<getName()<<" come and open the door\n";}
	};

	//一个派生子类,实际观察者
	class CBaby : public CPerson{
	public:
		CBaby(const std::string& vName) : CPerson(vName) {}
		virtual void act() {std::cout<<"Baby "<<getName()<<" weak up and cry\n";}
	};

	//被观察者基类
	class CRing
	{
	public:
		CRing() {}
		virtual ~CRing() {}

		void ring()//触发信号
		{
			__notifyV();
		}

		//增加观察者
		void add(CPerson* vPerson) {m_Person.push_back(vPerson);}

	private:
		std::vector<CPerson*> m_Person;

		//通知所有观察者
		virtual void __notifyV()
		{
			std::vector<CPerson*>::iterator It = m_Person.begin();
			while (It != m_Person.end()) (*It++)->act();
		}
	};

	class CGuest : public CRing
	{
	public:
		CGuest() {}
		virtual ~CGuest() {}
	};

	void test()
	{
		CGuest Guest;
		CNurse Nurse("LiSi");
		CBaby  Baby("ZhangSan");

		Guest.add(&Nurse);
		Guest.add(&Baby);
		Guest.ring();//触发信号
	}
}</span>
为什么要用boost::signals呢?原因是上面这个代码耦合性强,用boost::signals可以来降低耦合性:

<span style="font-size:14px;">#pragma once

#include <algorithm>
#include <boost/ref.hpp>
#include <boost/bind.hpp>
#include <boost/signals2.hpp>

namespace Test_Signal_work
{
	class CGate
	{
		typedef boost::signals2::signal<void(bool, const std::string&)> signal_type;
		typedef signal_type::slot_type slot_type;
	public:
		boost::signals2::connection connect(const slot_type& vSlot)
		{
			return m_EnterOrLiveSig.connect(vSlot);
		}

		void enter(const std::string& vCarId)
		{
			m_EnterOrLiveSig(true, vCarId);
		}

		void leave(const std::string& vCarId)
		{
			m_EnterOrLiveSig(false, vCarId);
		}
	private:
		signal_type m_EnterOrLiveSig;
	};

	class CCarInformation
	{
		typedef boost::signals2::signal<void(const std::string&)> signal_type;
		typedef signal_type::slot_type slot_type;
		typedef std::vector<std::string> cars_type;
	public:
		boost::signals2::connection connect(const slot_type& vSlot) 
		{
			return m_Sig.connect(vSlot);
		}

		void active(bool vEnter, const std::string& vCarId) 
		{
			vEnter ? enter(vCarId) : leave(vCarId);
		}

		//不要这个也行,可以用boost::bind
		void operator()(bool vEnter, const std::string& vCarId) 
		{
			active(vEnter, vCarId);
		}

	private:
		void enter(const std::string& vCarId) 
		{
			cars_type::iterator It = std::find(m_CarsInfo.begin(), m_CarsInfo.end(), vCarId);
			if (It == m_CarsInfo.end()) 
			{
				m_CarsInfo.push_back(vCarId);
				std::cout << "car " << vCarId << " enter!" << std::endl;
			}
			else
			{
				m_Sig(vCarId);
			}
		}

		void leave(const std::string& vCarId) 
		{
			cars_type::iterator It = std::find(m_CarsInfo.begin(), m_CarsInfo.end(), vCarId);
			if (It != m_CarsInfo.end()) 
			{
				std::cout << "car " << vCarId << " leave!" << std::endl;
				m_CarsInfo.erase(It);
			}
			else
			{
				m_Sig(vCarId);
			}
		}

	private:
		signal_type m_Sig;
		cars_type m_CarsInfo;
	};

	class CGuard 
	{
	public:
		CGuard(const std::string& vName) : m_GuardName(vName) {}
		void active(const std::string& vCarId)
		{
			std::cout << m_GuardName << " knew that " << "there is an exception with car " << vCarId << std::endl;
		}

		void operator()(const std::string& vCarId) 
		{
			active(vCarId);
		}
	private:
		std::string m_GuardName;
	};

	void test_fun_1()
	{
		CGuard ZS("ZhangSan");
		CGuard LS("LiSi");

		CCarInformation Info;

		CGate Gate1;
		CGate Gate2;

		Gate1.connect(boost::ref(Info));
		Gate2.connect(boost::ref(Info));

		Info.connect(LS);
		Info.connect(ZS);

		//测试1
		Gate1.enter("CA1001");
		Gate2.enter("CB1002");
		Gate1.leave("CB1002");
		//测试2
		Gate2.leave("CA1003");
		Gate1.enter("CA1004");
		Gate2.enter("CA1004");
		Gate1.leave("CA1004");
	}
}</span>


在看第二个例子:

题目2:模拟简单的停车场监视器

描述:

车辆进入或离开停车场时,监视器将收到一个通知信息(如唯一标记这辆车的车牌号,进入或离开时间,这里仅用车牌号),这样监视器才能跟踪每两车的进入和离开,并且监视器能够在有人进行欺骗行为时出发警报通知保安。

一个简单的停车场监视器可以用三个类来实现:CGate,CCarInformation,CGuard。

 

class CGate

{

public:

   void enter(const std::string& vCarId);

   void leave(const std::string& vCarId);

};

CGate必须有enter和leave函数,把车辆的信息发送给CCarInformation。

 

class CCarInformation

{

public:

   void active(bool vEnter, const std::string& vCarId);

private:

   std::vector<std::string> m_CarsInfo;

};

CcarInformation收到CGate的信息时必须有一个active来采取相应的措施,比如某辆车的信息异常侧发出警报通知CGuard。

 

class CGuard

{

public:

   void active(const std::string& vCarId);

private:

   std::string m_GuardName;

};

如果CcarInformation发出警报则CGuard必须做出反应。

要求实现功能:

   停车场有两个门,每个门都可以进出,并且把车辆的出入信息通知给CCarInformation来跟踪每辆车。同一辆车两次进入(或两次离开)时,发出警报通知所有保安。每个门有一个保安。

测试样例:

  

int main()

{

   //测试1

   Gate1.enter("CA1001");

   Gate2.enter("CB1002");

   Gate1.leave("CB1002");

    //测试2

   Gate2.leave("CA1003");

   Gate1.enter("CA1004");

   Gate2.enter("CA1004");

   Gate1.leave("CA1004");

}

测试1输出:

   car CA1001 enter!

   car CB1002 enter!

   car CB1002 leave!

测试2输出:

   ZhanSan knew that there is an exception with car CA1003!

   LiSi knew that there is an exception with car CA1003!

   car CA1004 enter!

   ZhanSan knew that there is an exception with car CA1004!

   LiSi knew that there is an exception with car CA1004!

   car CA1004 leave!

这个例子比前一个例子稍微复杂:

#pragma once

#include <algorithm>
#include <boost/ref.hpp>
#include <boost/bind.hpp>
#include <boost/signals2.hpp>

namespace Test_Signal_work
{
	class CGate
	{
		typedef boost::signals2::signal<void(bool, const std::string&)> signal_type;
		typedef signal_type::slot_type slot_type;
	public:
		boost::signals2::connection connect(const slot_type& vSlot)
		{
			return m_EnterOrLiveSig.connect(vSlot);
		}

		void enter(const std::string& vCarId)
		{
			m_EnterOrLiveSig(true, vCarId);
		}

		void leave(const std::string& vCarId)
		{
			m_EnterOrLiveSig(false, vCarId);
		}
	private:
		signal_type m_EnterOrLiveSig;
	};

	class CCarInformation
	{
		typedef boost::signals2::signal<void(const std::string&)> signal_type;
		typedef signal_type::slot_type slot_type;
		typedef std::vector<std::string> cars_type;
	public:
		boost::signals2::connection connect(const slot_type& vSlot) 
		{
			return m_Sig.connect(vSlot);
		}

		void active(bool vEnter, const std::string& vCarId) 
		{
			vEnter ? enter(vCarId) : leave(vCarId);
		}

		//不要这个也行,可以用boost::bind
		void operator()(bool vEnter, const std::string& vCarId) 
		{
			active(vEnter, vCarId);
		}

	private:
		void enter(const std::string& vCarId) 
		{
			cars_type::iterator It = std::find(m_CarsInfo.begin(), m_CarsInfo.end(), vCarId);
			if (It == m_CarsInfo.end()) 
			{
				m_CarsInfo.push_back(vCarId);
				std::cout << "car " << vCarId << " enter!" << std::endl;
			}
			else
			{
				m_Sig(vCarId);
			}
		}

		void leave(const std::string& vCarId) 
		{
			cars_type::iterator It = std::find(m_CarsInfo.begin(), m_CarsInfo.end(), vCarId);
			if (It != m_CarsInfo.end()) 
			{
				std::cout << "car " << vCarId << " leave!" << std::endl;
				m_CarsInfo.erase(It);
			}
			else
			{
				m_Sig(vCarId);
			}
		}

	private:
		signal_type m_Sig;
		cars_type m_CarsInfo;
	};

	class CGuard 
	{
	public:
		CGuard(const std::string& vName) : m_GuardName(vName) {}
		void active(const std::string& vCarId)
		{
			std::cout << m_GuardName << " knew that " << "there is an exception with car " << vCarId << std::endl;
		}

		void operator()(const std::string& vCarId) 
		{
			active(vCarId);
		}
	private:
		std::string m_GuardName;
	};

	void test_fun_1()
	{
		CGuard ZS("ZhangSan");
		CGuard LS("LiSi");

		CCarInformation Info;

		CGate Gate1;
		CGate Gate2;

		Gate1.connect(boost::ref(Info));
		Gate2.connect(boost::ref(Info));

		Info.connect(LS);
		Info.connect(ZS);

		//测试1
		Gate1.enter("CA1001");
		Gate2.enter("CB1002");
		Gate1.leave("CB1002");
		//测试2
		Gate2.leave("CA1003");
		Gate1.enter("CA1004");
		Gate2.enter("CA1004");
		Gate1.leave("CA1004");
	}
}




<think>我们正在处理一个关于在多线程环境下使用boost::asio和sdbusplus的问题。用户要求提供使用指南。根据提供的引用,我们可以总结出以下几点: 1. 引用[1]展示了如何使用boost::asio创建TCP端点(endpoint)和socket。 2. 引用[2]讨论了多线程环境下使用io_context的两种方式:一种是多个线程共享一个io_context,并使用strand来保证处理程序的顺序执行;另一种是在主线程运行io_context,而在其他线程中投递异步操作,此时需要注意线程安全(使用原子操作等)。 3. 引用[3]提到,boost::asio中的对象(如定时器)与特定的io_context关联,因此在创建这些对象时需要传入io_context。 另外,sdbusplus是一个基于sd-bus的C++封装库,用于D-Bus通信。它本身是线程安全的吗?通常,D-Bus库要求连接(connection)在同一时间只能被一个线程使用,或者需要显式地启用多线程支持并处理锁。 结合boost::asio和sdbusplus在多线程环境下的使用,我们可能需要考虑以下方面: - 如何设置boost::asio的io_context在多线程环境下运行? - 如何确保sdbusplus的D-Bus连接在多线程中安全使用? - 如何将boost::asio的事件循环与sdbusplus的事件循环整合(如果需要)? 由于sdbusplus有自己的事件循环(通常基于sd-bus的事件循环),而boost::asio也有自己的事件循环(io_context),我们可能需要在一个线程中同时处理两个事件循环,或者将两个事件循环整合。但更常见的做法是让它们分别运行在不同的线程中,并通过线程安全的方式通信。 然而,用户的问题集中在“多线程使用指南”,因此我们将重点放在如何安全地在多线程中使用这两个库。 ### 多线程使用boost::asio 根据boost::asio的文档,有以下几种多线程模型: 1. **单io_context,多线程运行io_context::run**:多个线程同时调用同一个io_context的run方法。这样,完成处理程序(completion handlers)可能会被并发执行。因此,对于共享资源,需要使用锁(如互斥量)或strand(一种特殊的执行器,用于确保处理程序按顺序执行)来保证线程安全。 ```cpp boost::asio::io_context io_context; // ... 将一些异步操作投递到io_context中 // 创建多个线程运行io_context std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_context]() { io_context.run(); }); } // 等待所有线程结束 for (auto& t : threads) { t.join(); } ``` 当使用这种方式时,如果多个处理程序访问共享资源,应该使用strand来包装: ```cpp boost::asio::io_context::strand my_strand(io_context); // 然后使用strand的wrap函数来包装处理程序,确保这些处理程序不会并发执行 my_socket.async_read_some(..., my_strand.wrap([](auto...){ ... })); ``` 2. **每个线程一个io_context**:每个线程有自己的io_context,这样每个io_context都是独立运行的。这种模型通常用于将特定的资源绑定到特定的线程(例如,每个连接绑定一个线程)。这种方式避免了不同线程间的资源竞争,但线程间通信需要额外的机制(如队列)。 ### 多线程使用sdbusplus sdbusplus的D-Bus连接(sdbusplus::bus_t)通常不是线程安全的。根据sd-bus的文档,一个总线连接(bus connection)不应该在多个线程中同时使用,除非显式地启用多线程支持并加锁。但是,sdbusplus本身并没有提供内置的多线程支持。因此,推荐的做法是: - 每个线程使用自己的bus_t对象(即每个线程创建自己的D-Bus连接),或者 - 在多个线程间共享一个bus_t对象,但必须确保同一时间只有一个线程在调用该对象的方法(通过将所有的D-Bus操作分派到同一个io_context的strand中执行,或者使用一个专门的线程来处理D-Bus连接)。 然而,创建多个D-Bus连接可能会增加系统负担,并且可能不是所有情况下都允许(例如,某些总线只能有一个连接)。 因此,更常见的做法是使用一个单独的线程来处理D-Bus连接,然后通过消息队列或其他线程间通信机制将请求从其他线程传递给这个专用线程。 ### 结合boost::asio和sdbusplus 如果我们希望在一个应用程序中同时使用boost::asio和sdbusplus,并且希望它们都能在多线程环境下工作,我们可以考虑以下架构: 1. 创建一个io_context对象,用于处理所有的异步I/O(如网络、定时器等)。 2. 创建一个sdbusplus::bus_t对象,用于D-Bus通信。 3. 将sdbusplus::bus_t与boost::asio整合:sdbusplus的bus_t可以获取底层的文件描述符(通过`int get_fd()`),然后我们可以将这个文件描述符注册到boost::asio的io_context中,以便在D-Bus有事件时通知asio,然后我们可以在asio的事件循环中处理D-Bus事件。 具体步骤: - 获取bus_t的文件描述符:`int fd = bus.get_fd();` - 创建一个boost::asio::posix::stream_descriptor对象,将其关联到io_context和这个文件描述符。 - 使用async_wait或async_read_some来监视这个文件描述符的可读事件。当可读时,调用`bus.process()`来处理D-Bus事件(注意:`bus.process()`处理一个事件,然后返回,因此可能需要循环处理直到没有事件)。 - 重复注册异步读取。 这样,我们就可以在boost::asio的事件循环中处理D-Bus事件。 4. 多线程处理:我们可以使用多个线程运行io_context的run方法。但是,由于D-Bus连接(bus_t)不是线程安全的,我们需要确保所有对bus_t的操作(包括`process()`和任何发送消息的方法)都在同一个strand中执行,或者在一个线程中执行(即只在一个线程中运行io_context,而其他线程通过投递任务到该io_context来间接操作bus_t)。 ### 示例代码框架 下面是一个简化的示例,展示如何将sdbusplus::bus_t整合到boost::asio的事件循环中,并在多线程环境下运行(但确保bus_t操作在一个strand中执行): ```cpp #include <boost/asio.hpp> #include <sdbusplus/asio/connection.hpp> #include <sdbusplus/bus.hpp> #include <thread> int main() { boost::asio::io_context io_context; // 创建sdbusplus的bus对象,使用asio的封装:sdbusplus::asio::connection // 注意:sdbusplus::asio::connection已经封装了与asio的整合 auto bus = std::make_shared<sdbusplus::asio::connection>(io_context); // 使用bus对象注册D-Bus对象和方法... // 创建strand,用于保护对bus的操作(实际上,sdbusplus::asio::connection内部可能已经做了保护,但为了安全,我们仍然使用strand) auto strand = std::make_shared<boost::asio::io_context::strand>(io_context); // 在多个线程中运行io_context std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_context]() { io_context.run(); }); } // 在主线程中,我们可以投递一些操作到strand中,这些操作将安全地访问bus boost::asio::post(*strand, [bus]() { // 在strand中安全地使用bus // 例如:调用D-Bus方法 }); // ... 其他操作 // 等待所有线程结束 for (auto& t : threads) { t.join(); } return 0; } ``` 注意:sdbusplus库中有一个`asio`子目录,其中提供了`connection`类,这个类已经封装了与boost::asio的整合。因此,我们不需要手动注册文件描述符到asio,因为`connection`内部已经处理了。 ### 总结 在多线程环境下使用boost::asio和sdbusplus,关键点在于: - 使用boost::asio的io_context在多线程中运行(多个线程调用run),并使用strand来保证对共享资源(包括sdbusplus的bus对象)的访问是线程安全的。 - 使用`sdbusplus::asio::connection`来创建bus连接,它已经与boost::asio整合,因此可以直接在asio的事件循环中处理D-Bus事件。 - 所有对bus对象的操作(包括调用D-Bus方法、处理信号等)都应该通过同一个strand来投递,以确保这些操作不会并发执行。 这样,我们就可以安全地在多线程应用中使用boost::asio和sdbusplus了。 ### 相关问题 1. 如何将sdbusplus的D-Bus事件循环整合到boost::asio中? 2. 在多线程中使用sdbusplus的bus_t对象有哪些注意事项? 3. 除了使用strand,还有哪些方法可以保证boost::asio处理程序的线程安全?</think>### 多线程环境下使用 Boost.Asio 和 sdbusplus 指南 #### 一、Boost.Asio 多线程核心机制 1. **io_context 多线程模型** - **共享 io_context**:多个线程共同执行 `io_context::run()`,需配合 `strand` 保证处理程序顺序执行 ```cpp boost::asio::io_context io_ctx; boost::asio::io_context::strand my_strand(io_ctx); // 创建strand[^2] // 线程池运行io_context std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_ctx] { io_ctx.run(); }); } ``` - **独立 io_context**:每个线程拥有专属 `io_context`,避免资源竞争 ```cpp std::vector<std::unique_ptr<boost::asio::io_context>> io_contexts; for (int i = 0; i < 4; ++i) { io_contexts.push_back(std::make_unique<boost::asio::io_context>()); std::thread([ctx=io_contexts.back().get()] { ctx->run(); }).detach(); } ``` 2. **线程安全对象绑定** 需确保异步对象(如 `socket`/`timer`)与特定 `io_context` 关联[^3]: ```cpp boost::asio::steady_timer timer(io_ctx); // 绑定到io_context timer.expires_after(std::chrono::seconds(1)); timer.async_wait([](auto...){ /* 处理程序 */ }); ``` #### 二、sdbusplus 多线程实践 1. **D-Bus 连接管理** - **单连接多线程**:使用 `sdbusplus::asio::connection` 封装,通过 `strand` 串行化访问 ```cpp auto bus = std::make_shared<sdbusplus::asio::connection>(io_ctx); boost::asio::post(my_strand, [bus] { auto method = bus->new_method_call("service", "/path", "interface", "method"); method.append(/* 参数 */); bus->call(method); // 安全调用 }); ``` - **多连接独立线程**:每个线程创建专属 D-Bus 连接 ```cpp void worker_thread() { sdbusplus::asio::connection local_bus(io_ctx_local); // 独立使用local_bus } ``` 2. **信号处理同步** 使用 `asio::signal_set` 绑定到 `strand`: ```cpp boost::asio::signal_set signals(io_ctx, SIGINT, SIGTERM); signals.async_wait(my_strand.wrap([](auto, auto){ /* 安全处理信号 */ })); ``` #### 三、整合架构示例 ```mermaid graph TD A[主线程] -->|创建| B[io_context] A -->|创建| C[sdbusplus::connection] B --> D[线程池 run] C -->|绑定| B D -->|strand 封装| E[DBus 方法调用] D -->|strand 封装| F[定时器操作] D -->|strand 封装| G[信号处理] ``` #### 四、关键注意事项 1. **共享资源保护** - 跨线程数据访问需使用 `std::atomic` 或互斥锁 - DBus 对象路径注册需在同一个线程完成 2. **性能优化** - I/O 密集型任务:共享 `io_context` + `strand` 分组[^2] - 计算密集型任务:独立 `io_context` 隔离负载 3. **错误处理** ```cpp timer.async_wait(my_strand.wrap([](boost::system::error_code ec) { if (ec) { // 统一错误处理路径 } })); ``` > **最佳实践**:对于DBus代理对象,推荐使用 `sdbusplus::asio::proxy` 自动匹配 `io_context` 和 `strand`,简化线程安全管理。 --- ### 相关问题 1. 如何避免 `io_context` 在多线程中的资源竞争问题? 2. `sdbusplus::asio::connection` 在哪些场景下需要手动加锁? 3. 如何设计超时机制整合 Boost.Asio 定时器和 D-Bus 方法调用? 4. 多线程环境下 D-Bus 信号订阅有哪些特殊注意事项? [^1]: 创建socket时需绑定io_context [^2]: 多线程分组需使用strand保证顺序 [^3]: 定时器等对象必须关联特定io_context
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值