最近写代码使用到了线程操作,并且了解了一下ROS的线程管理,在这里记录一下
C++多线程
主要就是线程与线程锁
1)线程
thread,join,detach
thread是用来开辟线程的
join的使用场景就是:当你需要等待子线程执行完时再执行主线程;
detach的使用场景是:你不想让正在执行的子线程随着局部线程句柄的销毁而销毁
2)线程锁
mutex,lock,unlock
当同名锁区间内同时调用共享资源时,就会互斥,即使调用的并不是同一个共享资源也会互斥,即顺序阻塞;不同名的锁之间则不会有影响。所以这里的建议就是:尽可能多的精简锁住的代码,对于不同变量可以选择设专锁,从而减少不必要的阻塞
另外,我们经常会在互斥锁解锁后,手动通知一下在buffer队列中阻塞的线程,这样能够确保他们(更快地)开始工作,如下:
condition_variable sig_buffer;
void fun()
{
mtx_buffer.lock();
process();
mtx_buffer.unlock();
sig_buffer.notify_all(); //可加可不加,建议加上
}
3)一些技巧
知道自己开了多少线程是一个很重要的事,然而C++没有提供现成线程动态统计工具,但我们可以在代码中定义一个全局变量或者静态变量来记录当前线程数量,并在创建和销毁线程的时候进行相应的加减操作。
可以定义一个结构体,来存放函数参数、线程句柄、处理结果、用时和是否结束的bool标志位。并且使用定义在全局的容器来存储他们,一个很实际的问题是,直接把 “在线程分配时定义在局部的结构体” pushback到容器中并不好,虽然pushback是将struct拷贝到堆区,但是你分配线程传参数的时候传入的是局部变量,其实和你容器中存放的并不是同一个struct,这就导致无法记录和返回结果,一种比较好的方式是:在使用new直接创建到堆区,然后将返回的地址pushback到容器中,并将地址当作参数在线程分配时,作为参数传入处理函数。(这是今天写代码遇到的实际问题,以及想到的解决方案)
ROS线程管理
一般而言,ROS的一个节点就是一个线程,除了在节点内手动使用C++开辟线程外,一个节点内有时也会自动开辟线程做事情,主要就是callback函数,callback主要分两种情况:话题通信的callback 和 定时器的callback ,前者是另开线程工作的,后者是在主程序中阻塞工作的
不过请注意,关于话题通信的回调函数,一种回调函数只会开辟一个线程,当回调函数调用的频率过高时,会存入队列依次执行;而定时器回调如果频率过高,导致当前回调未结束下一个回调就已经触发时,这是程序就会直接进入新的回调函数,老毁掉直接就会被中断不再执行了。(不会有递归式的返回,而是直接中断开启新的)
另外,在ROS的roscpp中提供了ThreadPool,ThreadPool是一个线程池,是C++线程操作的封装,用于管理多个线程的分配和回收。ThreadPool可以用于管理并发的ROS节点,从而提高节点的性能和稳定性。基本使用操作如下:
//创建ThreadPool对象,其中,num_threads为线程池中的线程数。
ros::ThreadPool thread_pool(num_threads);
//向线程池中添加任务,其中,callbackFunction为需要执行的回调函数,this为回调函数所在的节点对象指针。
thread_pool.queueJob(boost::bind(&MyNode::callbackFunction, this, _1));
//等待所有任务执行完毕,此函数将阻塞当前线程,直到线程池中的所有任务执行完毕。
thread_pool.waitForEmptyQueue();