多线程;顺序容器;智能指针

简介: 【10月更文挑战第14天】多线程的创建创建线程比较简单,C++提供头文件thread,使用std的thread实例化一个线程对象创建。std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。

多线程的创建创建线程比较简单,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_ptr
shared_ptr&lt;int&gt; p3 =make_shared&lt;int&gt;(42)
//指向一个值为"99999"的string的shared_ptr
shared_ptr&lt;int&gt; p4 =make_shared&lt;string&gt;(5,'9')
//指向一个值为0的int的shared_ptr
shared_ptr&lt;int&gt; p3 =make_shared&lt;int&gt;()

auto和make_shared通常用auto定义一个对象来保存make_shared的结果

//指向一个值为24的int的shared_ptr
auto p6 =make_shared&lt;int&gt;(42)

shared_ptr的拷贝和赋值1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

auto p=make_shared&lt;int&gt;(42) //p指向的对象只有p一个引用者
auto q(p) //p和q指向相同对象,此对象有两个引用者

检查 shared_ptr 对象的引用计数

p1.use_count();

完整实例

#include &lt;iostream&gt;
#include  &lt;memory&gt; // 需要包含这个头文件
 
int main()
{
  // 使用 make_shared 创建空对象
  std::shared_ptr&lt;int&gt; p1 = std::make_shared&lt;int&gt;();
  *p1 = 78;
  std::cout &lt;&lt; "p1 = " &lt;&lt; *p1 &lt;&lt; std::endl; // 输出78
 
  // 打印引用个数:1
  std::cout &lt;&lt; "p1 Reference count = " &lt;&lt; p1.use_count() &lt;&lt; std::endl;
 
  // 第2个 shared_ptr 对象指向同一个指针
  std::shared_ptr&lt;int&gt; p2(p1);
 
  // 下面两个输出都是:2
  std::cout &lt;&lt; "p2 Reference count = " &lt;&lt; p2.use_count() &lt;&lt; std::endl;
  std::cout &lt;&lt; "p1 Reference count = " &lt;&lt; p1.use_count() &lt;&lt; std::endl;
 
  // 比较智能指针,p1 等于 p2
  if (p1 == p2) {
    std::cout &lt;&lt; "p1 and p2 are pointing to same pointer\n";
  }
 
  std::cout&lt;&lt;"Reset p1 "&lt;&lt;std::endl;
 
  // 无参数调用reset,无关联指针,引用个数为0
  p1.reset();
  std::cout &lt;&lt; "p1 Reference Count = " &lt;&lt; p1.use_count() &lt;&lt; std::endl;
  
  // 带参数调用reset,引用个数为1
  p1.reset(new int(11));
  std::cout &lt;&lt; "p1  Reference Count = " &lt;&lt; p1.use_count() &lt;&lt; std::endl;
 
  // 把对象重置为NULL,引用计数为0
  p1 = nullptr;
  std::cout &lt;&lt; "p1  Reference Count = " &lt;&lt; p1.use_count() &lt;&lt; std::endl;
  if (!p1) {
    std::cout &lt;&lt; "p1 is NULL" &lt;&lt; std::endl; // 输出
  }
  return 0;
}
相关文章
|
2月前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
50 1
|
3月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
4月前
|
传感器 数据处理 定位技术
多线程;顺序容器;智能指针
多线程的创建创建线程比较简单,C++提供头文件thread,使用std的thread实例化一个线程对象创建。 std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。 #include &lt;iostream&gt; #include &lt;thread&gt; #include &lt;stdlib.h&gt; //sleep using namespace std; void t1() //普通的函数,用来执行线程 { for (int i = 0; i &lt; 10; ++i)
多线程;顺序容器;智能指针
|
5月前
|
传感器 数据处理 定位技术
多线程;顺序容器;智能指针
多线程的创建创建线程比较简单,C++提供头文件thread,使用std的thread实例化一个线程对象创建。 std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。 #include &lt;iostream&gt; #include &lt;thread&gt; #include &lt;stdlib.h&gt; //sleep using namespace std; void t1() //普通的函数,用来执行线程 { for (int i = 0; i &lt; 10; ++i)
|
6天前
|
Ubuntu API 网络虚拟化
ubuntu22 编译安装docker,和docker容器方式安装 deepseek
本脚本适用于Ubuntu 22.04,主要功能包括编译安装Docker和安装DeepSeek模型。首先通过Apt源配置安装Docker,确保网络稳定(建议使用VPN)。接着下载并配置Docker二进制文件,创建Docker用户组并设置守护进程。随后拉取Debian 12镜像,安装系统必备工具,配置Ollama模型管理器,并最终部署和运行DeepSeek模型,提供API接口进行交互测试。
121 15
|
1月前
|
Ubuntu NoSQL Linux
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
159 6
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
|
1月前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
198 11
|
2月前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
96 8
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
|
2月前
|
存储 Kubernetes 开发者
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
Docker 是一种开源的应用容器引擎,允许开发者将应用程序及其依赖打包成可移植的镜像,并在任何支持 Docker 的平台上运行。其核心概念包括镜像、容器和仓库。镜像是只读的文件系统,容器是镜像的运行实例,仓库用于存储和分发镜像。Kubernetes(k8s)则是容器集群管理系统,提供自动化部署、扩展和维护等功能,支持服务发现、负载均衡、自动伸缩等特性。两者结合使用,可以实现高效的容器化应用管理和运维。Docker 主要用于单主机上的容器管理,而 Kubernetes 则专注于跨多主机的容器编排与调度。尽管 k8s 逐渐减少了对 Docker 作为容器运行时的支持,但 Doc
176 5
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档