C++学习笔记——多线程(1)
目前在做推理引擎开发相关的工作,这块内容的话,对工程能力的要求还是比较高的,不再像以前只是写一些Python脚本训训模型就可以了,而且深入了解C++之后,也能感受到Python较C++暴露出的缺点,另一方面,由于模型推理所需的高效性,目前推理引擎的开发基本上都是用C++来实现,而且其中绕不开的一个难点就是多线程。这个系列我打算将我学习C++多线程开发的历程整理成文章,梳理相关知识点并整合到已有的知识体系中。
1. 线程和进程
线程和进程是操作系统的概念,这部分知识应该在学CSAPP的时候能够学习到,从另一个角度来说,假如已经编写了一个main.cpp文件,并且里面定义了main函数,然后通过编译该文件后就可以生成一个可执行文件(程序)main.o,当我们在终端中运行./main.o后,该可执行文件便会被操作系统加载到内存中,保存在一个相对独立的内存区域(虚拟地址空间),该空间的起始地址是0x00, 在32位操作系统上一个进程分配的空间大小最大为4GB,如下图所示,这里面的地址都是相对地址,真正访问的时候是会映射到内存物理地址上去的,这种策略可以有效防止多个进程运行时的地址重叠问题。这个虚拟地址空间中最重要的几个部分:
1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap):般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回 收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。
4、文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区 :存放函数体的二进制代码。
进程的虚拟地址空间
当程序加载到内存后,会自动开启一个主线程用来执行main函数中的代码,程序按照代码逻辑依次执行直至退出main函数,这个过程便称之为进程——即进行中的程序。可见程序运行时便会启动一个线程,此外我们也可以直接或间接调用创建线程的系统函数来在进程中继续创建除了主线程之外的其他线程,原理上这些线程可以共享该进程内存空间的所有东西。C++2.0已经提供了pthread库封装了多线程相关的一些函数。
2. C++多线程操作函数
多线程运行的目的是为了提高效率或者使不同模块的任务能够同时运行,一般情况下后台服务的目的都是前者,即提高服务吞吐量,服务部署之后是可以通过动态设置并发线程数来提高处理效率的。C++中常用的多线程相关的库函数有thread、mutex、future、conditional_variable等等。
①mutex: 创建一个互斥锁对象用于锁定临界区,临界区中主要包含了一些如果多个线程同时进入该区域,会导致操作混乱,所以在某一个线程进入临界区时,便给该区域上锁,当其他线程执行到要进入临界区时,发现该区域已经上锁,则阻塞在这个地方。拥有锁的线程直到退出该区域时再解锁,解锁之后,其他线程(包括已阻塞的线程才能继续运行)。原始的加解锁函数时lock()和unlock(), 但如果忘记了unlock则会导致死锁。所以比较常用的方法是创建临时对象 std::lock_guardhttps://zhuanlan.zhihu.com/p/367309864