创建线程的第四种方法:Executors线程池

博客给出了Executors相关代码示例,Executors在信息技术领域常用于线程池的创建等操作,代码示例能帮助开发者更好理解和运用Executors。

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

代码示例:

private static void ExecutorMethod() {
	//ExecutorService service = Executors.newSingleThreadExecutor(); //一池一线程
	ExecutorService service = Executors.newFixedThreadPool(3);			//一池三线程
	//ExecutorService service = Executors.newCachedThreadPool();     //一池N线程
	Future<Integer> result = null;
	try {
		for (int i = 1; i <= 15; i++) {
			result = service.submit( () -> {
				Thread.sleep(400);
				System.out.print(Thread.currentThread().getName());
				return new Random().nextInt(10);
			} );
			System.out.println("  ********result: "+result.get());
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		service.shutdown();
	}
}

private static void ScheduledExecutorMethod() {
	ScheduledExecutorService service = Executors.newScheduledThreadPool(5); //带延时的线程池
	Future<Integer> result = null;
	try {
		for (int i = 1; i <= 15; i++) {
			result = service.schedule(() -> {
				System.out.print(Thread.currentThread().getName());
				return new Random().nextInt(10);
			}, 2, TimeUnit.SECONDS);
			System.out.println("  ********result: "+result.get());
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		service.shutdown();
	}
}
<think>我们被要求回答关于ROS2中rclcpp::executors::MultiThreadedExecutor的作用和使用方法的问题。同时,我们有一些引用材料可以参考。根据引用[1]和[2]:-ROS2中有多种执行器(executor),包括SingleThreadedExecutor和MultiThreadedExecutor(在rclcpp中还有StaticSingleThreadedExecutor)。-SingleThreadedExecutor使用一个线程来处理多个回调(callback),即所有回调在一个线程中顺序执行。-MultiThreadedExecutor可以创建多个线程线程数可配置)来并行处理多个回调,这样多个回调可以同时执行。引用[2]还提到:默认情况下,一个节点中的多个回调函数是互斥的,即一次只能执行一个回调。当需要同时运行多个回调时,可以使用MultiThreadedExecutor。引用[4]进一步说明了使用MultiThreadedExecutor时需要注意回调组(callback_group)的设置。回调组有两种类型:-MutuallyExclusive(互斥):组内的回调不能同时执行(即同一时间只能执行一个)。-Reentrant(可重入):组内的回调可以同时执行。使用MultiThreadedExecutor时,可以通过将不同的回调分配到不同的回调组(特别是Reentrant组)来实现真正的并行执行。此外,引用[3]提到了Qt事件循环和ROS2订阅回调一起使用时需要注意:不能在同一个线程中同时使用Qt的事件循环(如QApplication::exec())和ROS2的spin函数(如rclcpp::spin()),因为两者都是阻塞函数,会互相影响。现在,我们整理关于MultiThreadedExecutor的作用和使用方法:###作用MultiThreadedExecutor的主要作用是允许多个回调函数(如订阅消息的回调、服务回调、定时器回调等)在多个线程中并行执行,从而提高系统的响应能力和吞吐量。这对于计算密集型的回调或需要同时处理多个任务的情况非常有用。###使用方法1.**创建MultiThreadedExecutor对象**:在代码中实例化一个MultiThreadedExecutor对象。2.**将节点添加到执行器**:通过`add_node`方法将节点添加到执行器中。3.**配置回调组**(可选但重要):-默认情况下,一个节点的所有回调属于同一个MutuallyExclusive回调组,即使用MultiThreadedExecutor,这些回调也不会并行执行(因为属于同一个互斥组)。-为了实现并行,需要将不同的回调分配到不同的回调组(特别是Reentrant组)。可以通过创建Reentrant回调组,然后在创建订阅、定时器等时指定该回调组。4.**启动执行器**:调用执行器的`spin`方法,该方法会阻塞当前线程,直到执行器被停止。###示例代码以下是一个使用MultiThreadedExecutor的简单示例:```cpp#include"rclcpp/rclcpp.hpp"//创建一个节点,其中包含两个定时器回调classMyNode:publicrclcpp::Node{public:MyNode():Node("my_node"){//创建一个互斥回调组callback_group_mutex_=this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);//创建一个可重入回调组callback_group_reentrant_=this->create_callback_group(rclcpp::CallbackGroupType::Reentrant);//创建定时器1,使用互斥回调组(默认也可以不指定,这里为了演示)timer1_=this->create_wall_timer(std::chrono::seconds(1),std::bind(&MyNode::timer1_callback,this),callback_group_mutex_);//创建定时器2,使用可重入回调组timer2_=this->create_wall_timer(std::chrono::seconds(1),std::bind(&MyNode::timer2_callback,this),callback_group_reentrant_);}voidtimer1_callback(){//模拟耗时操作std::this_thread::sleep_for(std::chrono::milliseconds(500));RCLCPP_INFO(this->get_logger(),"Timer1callback");}voidtimer2_callback(){//模拟耗时操作std::this_thread::sleep_for(std::chrono::milliseconds(500));RCLCPP_INFO(this->get_logger(),"Timer2callback");}private:rclcpp::TimerBase::SharedPtrtimer1_;rclcpp::TimerBase::SharedPtrtimer2_;rclcpp::CallbackGroup::SharedPtrcallback_group_mutex_;rclcpp::CallbackGroup::SharedPtrcallback_group_reentrant_;};intmain(intargc,char*argv[]){rclcpp::init(argc,argv);//创建节点autonode=std::make_shared<MyNode>();//创建线程执行器rclcpp::executors::MultiThreadedExecutorexecutor;executor.add_node(node);executor.spin();//阻塞,直到节点被关闭rclcpp::shutdown();return0;}```在这个例子中:-我们创建了一个节点,其中包含两个定时器回调。-第一个定时器使用互斥回调组(MutuallyExclusive),第二个定时器使用可重入回调组(Reentrant)。-当我们使用MultiThreadedExecutor时,由于两个定时器属于不同的回调组(且第二个是可重入组),它们可以在不同的线程中同时执行。注意:如果不指定回调组,默认情况下,两个定时器都属于同一个互斥回调组,那么即使使用MultiThreadedExecutor,它们也不能同时执行。因此,为了实现并行,我们需要显式地将它们分配到不同的回调组(至少有一个是可重入的)。###配置线程数MultiThreadedExecutor默认创建线程数等于系统支持的最大并发线程数(通常为CPU核心数)。我们也可以显式指定线程数:```cpp//指定线程数为4rclcpp::executors::MultiThreadedExecutorexecutor(rclcpp::ExecutorOptions(),4);```###注意事项1.**线程安全**:当多个线程同时执行回调时,必须确保回调函数是线程安全的,特别是当它们访问共享资源时,需要使用锁(如互斥锁)来保护。2.**避免阻塞**:在回调函数中避免长时间阻塞,否则会影响其他回调的执行。如果必须执行长时间操作,考虑使用异步操作或将其放到单独的线程中。3.**与Qt等GUI框架集成**:如引用[3]所述,Qt的事件循环和ROS2的spin都是阻塞函数,不能在同一线程中同时运行。解决方案可以是:-将ROS2的spin放在单独的线程中(使用MultiThreadedExecutor)。-或者使用Qt定时器定期调用`rclcpp::spin_some`(单线程方式)。###总结MultiThreadedExecutor通过多线程并行执行回调来提高性能,但需要正确设置回调组(使用Reentrant)来实现真正的并行。同时,需要注意线程安全和阻塞问题。最后,我们生成相关问题:</think>### ROS2 `rclcpp::executors::MultiThreadedExecutor` 详解 #### **作用与核心价值** `MultiThreadedExecutor` 是 ROS2 中用于**并行处理多个回调函数**的执行器,主要解决以下问题: 1. **并发性能瓶颈** 默认单线程执行器(`SingleThreadedExecutor`)会顺序执行回调函数,当节点包含多个高负载回调(如密集计算、阻塞 I/O)时,会导致响应延迟[^1][^2]。 2. **回调互斥问题** 同一节点的回调函数默认属于互斥回调组(`MutuallyExclusive callback_group`),无法并行执行[^4]。 3. **多节点协同需求** 当单个进程包含多个节点时,需要协调各节点的回调执行[^2]。 #### **使用方法** ##### **基础配置** ```cpp #include "rclcpp/executors/multi_threaded_executor.hpp" int main(int argc, char* argv[]) { rclcpp::init(argc, argv); // 创建节点 auto node1 = std::make_shared<MyNode>("node1"); auto node2 = std::make_shared<MyNode>("node2"); // 创建线程执行器(默认线程数 = CPU核心数) rclcpp::executors::MultiThreadedExecutor executor; // 添加节点到执行器 executor.add_node(node1); executor.add_node(node2); // 启动执行器(阻塞当前线程) executor.spin(); rclcpp::shutdown(); return 0; } ``` ##### **进阶配置** 1. **自定义线程数** 显式指定工作线程数量: ```cpp // 创建含4个工作线程的执行器 rclcpp::executors::MultiThreadedExecutor executor( rclcpp::ExecutorOptions(), 4 // 线程数 ); ``` 2. **配置回调组实现真并行** 关键步骤:将回调分配到**可重入组**(`Reentrant callback_group`)[^4]: ```cpp class MyNode : public rclcpp::Node { public: MyNode() : Node("my_node") { // 创建可重入回调组 auto reentrant_group = create_callback_group( rclcpp::CallbackGroupType::Reentrant); // 创建订阅并绑定到可重入组 sub1_ = create_subscription<MsgType>( "topic1", 10, [this](const MsgType::SharedPtr msg) { /* 回调1 */ }, rclcpp::SubscriptionOptions(), reentrant_group); // 指定回调组 // 另一个订阅使用相同回调组 sub2_ = create_subscription<MsgType>( "topic2", 10, [this](const MsgType::SharedPtr msg) { /* 回调2 */ }, rclcpp::SubscriptionOptions(), reentrant_group); // 同一组可并行 } }; ``` #### **工作机制** ```mermaid graph TD A[MultiThreadedExecutor] --> B[线程池] B --> C[线程1-处理回调A] B --> D[线程2-处理回调B] B --> E[线程3-处理回调C] C --> F[同一回调组内可并行] D --> F E --> F ``` 1. **线程池管理** - 创建 N 个工作线程(N=CPU核心数 by default) - 每个线程独立从回调队列中获取任务 2. **回调分发规则** - 相同 `MutuallyExclusive` 组:顺序执行 - 相同 `Reentrant` 组:可并行执行 - 不同回调组:总是并行执行 #### **典型应用场景** 1. **计算密集型节点** 如同时运行视觉处理 + 控制算法回调 2. **多节点聚合进程** 单进程包含多个独立节点(如导航+感知) 3. **混合阻塞操作** 需要同时处理网络I/O和实时计算的任务 #### **注意事项** 1. **线程安全问题** 并行回调中访问共享资源时**必须加锁**(如 `std::mutex`): ```cpp std::mutex data_mutex_; void callback() { std::lock_guard<std::mutex> lock(data_mutex_); // 操作共享数据 } ``` 2. **与Qt集成** 避免与Qt事件循环冲突[^3]: ```cpp // 错误:同一线程阻塞两个循环 executor.spin(); // ROS阻塞 QApplication::exec(); // Qt阻塞 // 正确:分离线程 std::thread ros_thread([&]() { executor.spin(); }); QApplication::exec(); // 主线程运行Qt ``` 3. **性能监控** 使用 `rqt_top` 或 `ros2 topic hz` 监控回调频率,确保负载均衡。 #### **与单线程执行器对比** | 特性 | `MultiThreadedExecutor` | `SingleThreadedExecutor` | |--------------------|-------------------------------|-------------------------------| | 线程数 | 可配置 (默认=CPU核心数) | 固定1线程 | | 回调并行能力 | ✅ (需配置Reentrant组) | ❌ | | CPU利用率 | 高 | 低 | | 适用场景 | 计算密集/多节点聚合 | 简单回调/低负载 | | 资源消耗 | 较高(线程管理开销) | 极低 | > **最佳实践**:对性能敏感节点使用 `Reentrant` 回调组 + 多线程执行器;简单节点用默认互斥组即可[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值