进程的内存空间布局

栈区:函数的运行时栈
共享内存映射区:用于存储进程的映射文件、共享内存、动态链接库等内容。也可以用于映射设备文件或其他资源。
堆区:malloc 分配的内存区域
数据区:保存全局变量和静态变量
代码区:代码编译后形成的机器指令
注意:数据段又分为已初始化的数据段和未初始化的数据段,即数据段存储程序中已初始化和未初始化的全局变量及静态变量。
线程的私有资源
栈:线程运行函数的栈区,用于存储该线程的局部变量、函数参数、返回地址等信息
寄存器:每个线程在CPU上执行时都会使用寄存器存储当前的执行状态,例如程序计数器(PC)
线程局部存储:线程局部存储允许线程拥有自己独立的全局变量副本,而这些变量在不同线程之间不会共享
线程间的共享资源

一个线程就是执行一个函数,进程中的线程就是对应着进程内存布局中的栈区,除去栈区是每个线程独有的资源,其余区域就属于多个线程共享的资源,下面逐一分析。
代码区
线程之间共享代码区,意味着程序中的任何一个函数都可以放到线程中去执行,不存在某个函数只能被特定线程执行的情况。
由于代码区是只读的,因此这个区域在多个线程之间不存在线程安全问题。
数据区
存放静态变量和全局变量,多个线程共享,可读可写,存在线程安全问题。
堆区
存放 malloc 申请的内存,多个线程只要拿到执行这块内存的指针,就可以操作这块内存数据(可读可写),存在线程安全问题。
动态链接库与文件
动态库的代码和数据都放在空闲区域,即共享内存映射区。这种第三方库的问题,就很难明确说是否线程安全,还得看调用者和被调用者代码的情况。
栈区
明明我们前面已经谈及 栈区是各个线程私有的,为什么还会纳入共享资源呢?
不同的进程的地址空间相互隔离,虚拟内存系统确保这点,但是不同的线程的栈区没有严格的隔离机制来保护。
因此如果一个线程能拿到来自另一个线程栈帧上的指针,那么该线程就可以改变另一个线程的栈区,也就是说这些线程可以任意修改本属于另一个线程栈区中的变量。
1 |
|
一个线程修改另一个线程的数据,真是相当危险,且难以排查。你以为是你写的函数有问题,结果是其他函数修改你的数据,这让人怎么去查?
如果你不足够熟悉涉及到的代码,debug 可是不那么容易弄出来的。
编写线程安全的代码
只使用线程的私有资源:函数内定义的局部变量
函数的局部参数:参数传递有值传递和引用传递,值传递是线程安全,引用传递不是线程安全
线程局部存储:虽然多线程共享,但是各自有各自的副本,确保是线程安全
函数的返回值:返回值没有问题,但是返回指针会存在线程安全问题
原子操作和同步互斥:原子操作是线程安全,同步和互斥是常见的保证线程安全的方法