「本文主要介绍在编写SLAM/VIO工程时用到的C++11多线程编程的内容,包括<mutex>互斥访问、unique_lock锁管理类、condition_variable条件变量、<atomic>原子操作等内容。」

之所以把C++的内容放到SLAM系列是因为这些C++11新特性其实在C++课程中是不会涉及的,通常是在实践中需要用到时再去查去学,因此对应SLAM工程中的需要,在这里整理一下以便后面查阅复习。

未经允许,请勿转载。

笔者上一篇文章介绍了C++多线程编程的「基础概念」,以及<thread>的简单使用,链接如下:

yikang:C++笔记——多线程编程(1)​zhuanlan.zhihu.com

一、std::mutex 互斥访问

<mutex>是C++标准程序库中的一个头文件,定义了C++11标准中一些互斥访问的类与方法。

其中std::mutex表示普通互斥锁,可以与std::unique_lock配合使用,把std::mutex放到unique_lock中时,mutex会自动上锁,unique_lock析构时,同时把mutex解锁。因此std::mutex可以保护同时被多个线程访问的共享数据,并且它独占对象所有权,不支持对对象递归上锁。

可以这样理解:各个线程在对共享资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。(下图来自网络)

常用的成员函数有:

  1. 构造函数:std::mutex不支持copy和move操作,最初的mutex对象处于unlocked状态。
  2. lock函数:互斥锁被锁定。如果线程申请该互斥锁,但未能获得该互斥锁,则申请调用的线程将阻塞(block)在该互斥锁上;如果成功获得该互诉锁,则该线程一直拥有互斥锁直到调用unlock解锁;如果该互斥锁已经被当前调用线程锁住,则产生死锁(deadlock)。
  3. unlock函数:互斥锁解锁,释放调用线程对该互斥锁的所有权。

一个简单的例子:

std::mutex mtx;  // 创建一个互斥锁
static void print_(int n, char c)
{mtx.lock();       // 申请对访问资源,上锁for (int i = 0; i<n; ++i) { std::cout << c; }std::cout << 'n';mtx.unlock();    // 对资源操作结束,解锁释放互斥锁的所有权
}

二、std::unique_lock 锁管理模板类

std::unique_lock为锁管理模板类,是对通用mutex的封装。

std::unique_lock对象以独占所有权的方式(unique owership)管理mutex对象的上锁和解锁操作,即在unique_lock对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而unique_lock的生命周期结束之后,它所管理的锁对象会被解锁。因此用unique_lock管理互斥对象,可以作为函数的返回值,也可以放到STL的容器中。

在使用条件变量std::condition_variable时需要使用std::unique_lock而不能使用std::lock_guard。

其常用的成员函数为:

  1. unique_lock构造函数:禁止拷贝构造,允许移动构造;
  2. lock函数:调用所管理的mutex对象的lock函数;
  3. unlock函数:调用所管理的mutex对象的unlock函数;

例如这样使用:

std::mutex mtx;   // 定义一个互斥锁
void print_thread_id(int id) {std::unique_lock<std::mutex> lck(mtx, std::defer_lock);  // 定义一个锁管理对象(参数2其实可以省略)lck.lock();std::cout << "thread #" << id << 'n';lck.unlock();
}

三、std::condition_variable 条件变量

<condition_variable>是C++标准程序库中的一个头文件,定义了C++11标准中的一些用于并发编程时表示条件变量的类与方法等。

条件变量的引入是为了作为并发程序设计中的一种控制结构。当多个线程访问同一共享资源时,不但需要用互斥锁实现独享访问以避免并发错误(竞争危害),在获得互斥锁进入临界区后还需要检验特定条件是否成立:

  1. 若不满足该条件,拥有互斥锁的线程应该释放该互斥锁,使用unique_lock函数把自身阻塞(block)并挂到条件变量的线程队列中
  2. 若满足该条件,拥有互斥锁的线程在临界区内访问共享资源,在退出临界区时通知(notify)在条件变量的线程队列中处于阻塞状态的线程,被通知的线程必须重新申请对该互斥锁加锁。

条件变量std::condition_variable用于多线程之间的通信,它可以阻塞一个或同时阻塞多个线程。std::condition_variable需要与std::unique_lock配合使用

常用成员函数:

(1)构造函数:仅支持默认构造函数。

(2)wait():当前线程调用wait()后将被阻塞,直到另外某个线程调用notify_*唤醒当前线程。当线程被阻塞时,该函数会自动调用std::mutex的unlock()释放锁,使得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(notify,通常是另外某个线程调用notify_*唤醒当前线程),wait()函数自动调用std::mutex的lock()。wait分为无条件被阻塞和带条件的被阻塞两种:

  1. 无条件被阻塞:调用该函数之前,当前线程应该已经对unique_lock<mutex> lck完成了加锁。所有使用同一个条件变量的线程必须在wait函数中使用同一个unique_lock<mutex>。该wait函数内部会自动调用lck.unlock()对互斥锁解锁,使得其他被阻塞在互斥锁上的线程恢复执行。使用本函数被阻塞的当前线程在获得通知(notified,通过别的线程调用 notify_*系列的函数)而被唤醒后,wait()函数恢复执行并自动调用lck.lock()对互斥锁加锁。
  2. 带条件的被阻塞:wait函数设置了谓词(Predicate),只有当pred条件为false时调用该wait函数才会阻塞当前线程,并且在收到其它线程的通知后只有当pred为true时才会被解除阻塞。因此,等效于while (!pred()) wait(lck).

(3)notify_all: 唤醒所有的wait线程,如果当前没有等待线程,则该函数什么也不做。

(4)notify_one:唤醒某个wait线程,如果当前没有等待线程,则该函数什么也不做;如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。

简单的说就是,当std::condition_variable对象的某个wait函数被调用的时候,它使用std::unique_lock(通过std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的std::condition_variable对象上调用了notification函数来唤醒当前线程。

四、std::atomic 原子操作

<atomic>是C++标准程序库中的一个头文件,定义了C++11标准中的一些表示线程、并发控制时进行原子操作的类与方法,主要声明了两大类原子对象:std::atomic和std::atomic_flag。

原子操作的主要特点是原子对象的并发访问不存在数据竞争,利用原子对象可实现数据结构的无锁设计。在多线程并发执行时,原子操作是线程不会被打断的执行片段。

(1)atomic_flag类

  1. 是一种简单的原子bool类型,只支持两种操作:test_and_set(flag=true)和clear(flag=false)。
  2. 跟std::atomic的其它所有特化类不同,它是锁无关的。
  3. 结合std::atomic_flag::test_and_set()和std::atomic_flag::clear(),std::atomic_flag对象可以当作一个简单的自旋锁(spin lock)使用。
  4. atomic_flag只有默认构造函数,禁用拷贝构造函数,移动构造函数实际上也禁用。
  5. 如果在初始化时没有明确使用宏ATOMIC_FLAG_INIT初始化,那么新创建的std::atomic_flag对象的状态是未指定的(unspecified),既没有被set也没有被clear;如果使用该宏初始化,该std::atomic_flag对象在创建时处于clear状态
  • test_and_set:返回该std::atomic_flag对象当前状态,检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false。该函数是原子的。
  • clear:清除std::atomic_flag对象的标志位,即设置atomic_flag的值为false。

(2)std::atomic类

  1. std::atomic提供了针对bool类型、整形(integral)和指针类型的特化实现。每个std::atomic模板的实例化和完全特化定义一个原子类型。
  2. 若一个线程写入原子对象,同时另一个线程从它读取,则行为良好定义。
  3. 原子对象的访问可以按std::memory_order所指定建立线程间同步,并排序非原子的内存访问。
  4. std::atomic可以以任何可平凡复制(Trivially Copyable)的类型T实例化。
  5. std::atomic既不可复制亦不可移动
  6. ATOMIC_VAR_INIT(val):可以由构造函数直接执行此宏初始化std::atomic对象。

std::atomic 常用的成员函数:

  1. std::atomic::store(val) 函数将参数 val 复制给原子对象所封装的值。
  2. std::atomic::load() 读取被原子对象封装的值。
  3. std::atomic::exchange(val) 读取并修改被封装的值,exchange 会将 val 指定的值替换掉之前该原子对象封装的值,并返回之前该原子对象封装的值,整个过程是原子的.
  4. atomic() 默认构造函数,由默认构造函数创建的 std::atomic 对象处于未初始化(uninitialized)状态,对处于未初始化(uninitialized)状态 std::atomic对象可以由 atomic_init 函数进行初始化。
  5. atomic (T val) 初始化构造函数,由类型 T初始化一个 std::atomic对象。
  6. atomic (const atomic&) 拷贝构造函数被禁用。

参考文献:

  1. https://blog.csdn.net/liuxuejiang158blog/article/details/17413149

2. https://blog.csdn.net/fengbingchun/article/details/73436710

3. https://www.cnblogs.com/wangshaowei/p/9593201.html

4. https://blog.csdn.net/fengbingchun/article/details/73695596

5. https://www.cnblogs.com/taiyang-li/p/5914331.html

6. https://blog.csdn.net/tanningzhong/article/details/78093774

为什么不能线程调用类的成员函数_SLAM从0到1——13.SLAM中的多线程编程(2)相关推荐

  1. 为什么不能线程调用类的成员函数_C++多线程编程之创建线程的几种方法

    点蓝色字关注"CurryCoder的程序人生" 微信公众号:CurryCoder的程序人生 怕什么真理无穷,进一寸有一寸的欢喜 1.线程基础知识 可执行程序运行起来,就会生成一个进 ...

  2. 函数指针调用类的成员函数

    1 在每个被调用函数之前加上static, 可以使成员函数脱离对象信息单独存在,虽然它属于这个类,但是没有附带上对象信息,但是前提是,static成员函数不能使用对象的信息(成员和函数). 2 使用一 ...

  3. 浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?

    有下面的一个简单的类: class CNullPointCall { public:     static void Test1();     void Test2();     void Test3 ...

  4. 怎么将一个类的成员函数作为指针传递给另一个类的成员函数

    今天帮同学解决了一个问题,怎么把一个类的成员函数作为指针传递给另一个类的成员函数. 以前只接触过C语言中的函数指针: #include <iostream.h> void add(int ...

  5. Cpp 对象模型探索 / 类普通成员函数的调用方式

    C++设计时有一个要求,类普通成员函数的调用性能要和全局函数差不多.所以编译器在处理类的普通成员函数的宗旨是将其当作全局函数来处理. 为了达到上述目的,编译器会对类的普通成员函数进行如下操作: 在函数 ...

  6. oc 协议 回调 静态成员_每日一问:c++类的成员函数,能作为线程的参数吗?

    问:类的成员函数可以传入线程参数吗? 回答: 如果c语言的全局函数,可以. 如果是类的静态成员函数,可以 如果是类的普通成员函数,不可以 为什么? <深入探索C++对象模型>中提到成员函数 ...

  7. 在一个类的成员函数中调用另一个类的成员函数

    假设你想在类A里调用类B的函数int f(x),两种办法: 1.class A::B  也就是说将B定义为A的父类, 这样你就可以自然的在A里面用f(x)了 2.class A { B B_ins; ...

  8. 处理菱形继承问题实现一个虚函数的覆盖及调用实现以下几个类的成员函数...

    #include <iostream> #include <string> using namespace std; 1.实现以下几个类的成员函数 2.实现一个虚函数的覆盖及调 ...

  9. 类的成员函数指针(比较深入)

    From:http://blog.csdn.net/hairetz/archive/2009/05/06/4153252.aspx 个人感觉对于类的成员函数指针这块讲解的比较深入详细 推荐阅读 / 先 ...

  10. C++11多线程,thread库; mutex类,成员函数lock(), unlock();unique_lock<mutex>模板类

    文章目录 进程和线程 1. 进程 2. 线程 C++11多线程编程 1. C++11新标准 2. 创建线程 1. 普通函数 2. 仿函数 3. 成员函数 4. 多线程数据保护(数据一致性) 进程和线程 ...

最新文章

  1. NumPy迎来重大版本更新
  2. python获取文件夹下文件_Python获取目录下的所有文件
  3. Qt 2D绘图功能简单总结
  4. 后端:SpringBoot 的@Value注解,高级特性,非常实用!!
  5. HDOJ 1012-1020
  6. linux安装elasticsearch5.5
  7. 含有js的英文单词_JavaScript 常用单词整理
  8. html语言可以干什么,JavaScript语言能做什么?
  9. [有限元] DistMesh Matlab 程序示例
  10. 【AI视野·今日CV 计算机视觉论文速览 第183期】28 Apr 2020
  11. 数据结构:单链表和双向链表
  12. puppet连载八:linux优化模块
  13. 给RABBITMQ发送消息时,设置请求头HEADER
  14. 联网生活方式下,消费者的7大关键需求
  15. 招聘网站数百万条敏感数据泄露,简历、×××扫描件统统曝光
  16. 开源非英文关键词编程语言
  17. Python并发机制的实现(一)——多进程
  18. Unity 2d - 基础 - 碰撞(一) - 针对性碰撞
  19. 来自NCBI GEO原始数据上传的一个“bug”!
  20. linux RAID管理与恢复误删除文件

热门文章

  1. Atitit 头像文件上传功能实现目录1. 上传文件原理 11.1. 界面ui 11.2. 预览实现 21.3. 保存头像文件php 21.4. 保存文件nodejs java 32
  2. atitit.提升研发管理的利器---重型框架 框架 类库的区别
  3. paip.java 多线程参数以及返回值Future FutureTask 的使用.
  4. paip.yxshopV4.7.1的安装不能用的问题
  5. 如果你感到ETF内卷了,聪明的你要换条路
  6. Julia :复合类型struct当索引时
  7. 2018美国基金业年鉴-数据图表简版
  8. “一云多Region”究竟能为企业解决什么问题?
  9. 一个线上SQL死锁异常分析:深入了解事务和锁
  10. Golang 性能分析工具简要介绍