多线程的创建创建线程比较简单,C++提供头文件 thread,使用 std 的 thread 实例化一个线程对象创建。
std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。
#include <iostream>#include <thread>#include <stdlib.h> //sleep using namespace std; void t1() //普通的函数,用来执行线程{ for (int i = 0; i < 10; ++i) { cout << "t1111\n"; sleep(1); }}void t2(){ for (int i = 0; i < 20; ++i) { cout << "t22222\n"; sleep(1); }}int main(){ thread th1(t1); //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1()) thread th2(t2); th1.join(); // 必须将线程join或者detach 等待子线程结束主进程才可以退出 th2.join(); cout << "here is main\n\n"; return 0;}
复制代码
LIO-SAM 中的线程操作
int main(int argc, char** argv){ ros::init(argc, argv, "lio_sam"); mapOptimization MO; ROS_INFO("\033[1;32m----> Map Optimization Started.\033[0m"); std::thread loopthread(&mapOptimization::loopClosureThread, &MO); std::thread visualizeMapThread(&mapOptimization::visualizeGlobalMapThread, &MO); ros::spin(); loopthread.join(); visualizeMapThread.join(); return 0;}
复制代码
两个线程主要运行的函数主要时回环检测线程和全局地图可视化线程,在每个线程内部都有互斥锁操作防止两个线程运行时对相同数据进行干扰
互斥锁 mutex 是用来保证线程同步的,防止不同的线程同时操作同一个共享数据
通过 mutex 可以方便的对临界区域加锁,std::mutex 类定义于 mutex 头文件,是用于保护共享数据避免从多个线程同时访问的同步原语。它提供了 lock,try_lock,unlock 等几个接口
#include<iostream>#include<thread>#include<mutex> using namespace std; mutex m;int cnt = 10; void thread1() { while (cnt > 5){ m.lock(); if (cnt > 0) { --cnt; cout << cnt << endl; } m.unlock(); }} void thread2() { while (cnt > 0) { m.lock(); if (cnt > 0) { cnt -= 10; cout << cnt << endl; } m.unlock(); }} int main(int argc, char* argv[]) { thread th1(thread1); //实例化一个线程对象th1,该线程开始执行 thread th2(thread2); th1.join(); th2.join(); cout << "main..." << endl; return 0;}
复制代码
aloam 中互斥锁的应用
void laserCloudCornerLastHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudCornerLast2){ mBuf.lock(); cornerLastBuf.push(laserCloudCornerLast2); mBuf.unlock();}
复制代码
在接收 laserCloudCornerLast2 时为了完整的接收到当前帧角点点云信息在开始接受时为了防止其他线程影响该数据使用 mBuf.lock();接受完后再打开 mBuf.unlock();
std::lock_guard 因为 mutex 需要在数据处理开始和结束时成对出现,当数据处理过程很长时容易遗忘造成危害,所以使用 std::lock_guard
要加锁的代码段,我们用{}括起来形成一个作用域,括号的开端创建 lock_guard 对象,把 mutex 对象作为参数传入 lock_guard 的构造函数即可
这就相当于下面代码
std::thread thread2([&]()){ for(int i=0;i<10000;i++) { mtx.lock(); ++num; mtx.unlock(); }});
复制代码
LIO-SAM 中的 std::lock_guard
//订阅imu里程计,由imuPreintegration积分计算得到的每时刻imu位姿 void odometryHandler(const nav_msgs::Odometry::ConstPtr& odometryMsg) { std::lock_guard<std::mutex> lock2(odoLock); odomQueue.push_back(*odometryMsg); }
复制代码
在接收里程计数据时防止其他线程对接受数据造成干扰,使用 std::lock_guard。该对象只在他所处的大括号内起作用
顺序容器概述
顺序容器使用原则通常,使用 vector 是最好的选择,除非你有很好的理由选择其他容器
Vector 由于一般情况下 vector 使用较多,首先介绍一些 vector 的使用
size()返回容器中元素数目;swap()交换两个容器的内容;begin()返回一个指向容器中第一个元素的迭代器;end()返回一个表示超过容器尾的迭代器(指向容器最后一个元素后面的那个元素位置);push_back()将元素添加到矢量(vector)末尾;erase()删除矢量中给定区间的元素;insert()插入指定区间的元素到指定位置;
复制代码
deque 在 slam 接收储存一些传感器数据时也是用到队列先进先出
deque 是一个的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector 容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。
用 sort 对 deque 排序
#include <iostream>#include <deque>#include <algorithm>using namespace std; void printDeque(const deque<int> &d){ for(deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { cout<<*it<<" "; } cout<<endl;} /* 回调函数 */bool compare(int v1, int v2){ return v1 > v2; /* 此时的排序是从大到小 */ /* 如果从小到大应该改为"v1 < v2" */} void test(){ deque<int> d; d.push_back(3); d.push_back(4); d.push_back(1); d.push_back(7); d.push_back(2); sort(d.begin(), d.end(), compare); printDeque(d);}
复制代码
assign()STL 中不同容器之间是不能直接赋值的,assign()可以实现不同容器但相容的类型赋值,如:
list<string> names;vector<const char*> oldstyle = { "I","love","you" };//names = oldstyle;错误!不同的类型不能执行"="操作names.assign(oldstyle.cbegin(), oldstyle.cend());list<string>::iterator it;for (auto it = names.begin(); names.begin() != names.end(); it++) cout << *it << " ";
复制代码
在 lego-loam 中有如下操作
segMsg.startRingIndex.assign(N_SCAN, 0);segMsg.endRingIndex.assign(N_SCAN, 0);
复制代码
智能指针概念以及为什么引入智能指针
shared_ptr 类的创建
shared_ptr<string> p1 //shared_ptr 指向string类型shared_ptr<list<int>> p2 //shared_ptr 指向int类型的list
复制代码
make_shared 函数使用最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的 shared_ptr。当要用 make_shared 时,必须指定想要创建的对象的类型,定义方式与模板类相同。在函数名之后跟一个尖括号,在其中给出类型。例如,调用 make_shared<string>时传递的参数必须与 string 的某个构造函数相匹配。如果不传递任何参数,对象就会进行值初始化。
//指向一个值为24的int的shared_ptrshared_ptr<int> p3 =make_shared<int>(42)//指向一个值为"99999"的string的shared_ptrshared_ptr<int> p4 =make_shared<string>(5,'9')//指向一个值为0的int的shared_ptrshared_ptr<int> p3 =make_shared<int>()
复制代码
auto 和 make_shared 通常用 auto 定义一个对象来保存 make_shared 的结果
//指向一个值为24的int的shared_ptrauto p6 =make_shared<int>(42)
复制代码
shared_ptr 的拷贝和赋值 1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加 1。2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减 1。如果引用计数变为 0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用 delete 函数删除该内存。
auto p=make_shared<int>(42) //p指向的对象只有p一个引用者auto q(p) //p和q指向相同对象,此对象有两个引用者
复制代码
检查 shared_ptr 对象的引用计数
完整实例
#include <iostream>#include <memory> // 需要包含这个头文件 int main(){ // 使用 make_shared 创建空对象 std::shared_ptr<int> p1 = std::make_shared<int>(); *p1 = 78; std::cout << "p1 = " << *p1 << std::endl; // 输出78 // 打印引用个数:1 std::cout << "p1 Reference count = " << p1.use_count() << std::endl; // 第2个 shared_ptr 对象指向同一个指针 std::shared_ptr<int> p2(p1); // 下面两个输出都是:2 std::cout << "p2 Reference count = " << p2.use_count() << std::endl; std::cout << "p1 Reference count = " << p1.use_count() << std::endl; // 比较智能指针,p1 等于 p2 if (p1 == p2) { std::cout << "p1 and p2 are pointing to same pointer\n"; } std::cout<<"Reset p1 "<<std::endl; // 无参数调用reset,无关联指针,引用个数为0 p1.reset(); std::cout << "p1 Reference Count = " << p1.use_count() << std::endl; // 带参数调用reset,引用个数为1 p1.reset(new int(11)); std::cout << "p1 Reference Count = " << p1.use_count() << std::endl; // 把对象重置为NULL,引用计数为0 p1 = nullptr; std::cout << "p1 Reference Count = " << p1.use_count() << std::endl; if (!p1) { std::cout << "p1 is NULL" << std::endl; // 输出 } return 0;}
复制代码
评论