Linux多线程
Linux多线程
01. 分页储存
由于连续分配内存方式,需要为用户进程分配的必须是一个连续的内存空间,这样多个进程在使用时会产生内(外)部碎片,无法满足后续进程需要,由此产生了非连续分配管理内存的方式。即将进程的逻辑地址空间划分为一个一个与页框大小相等的页面,页号从0开始。
1.1 单级页表
通过页号直接索引页表,然后找到与之对应的物理块号,与页内偏移结合得到物理地址。
如果采用一级页表存储页表项,在32位
机器中,页框大小为4KB
,页表项占4B
那么需要专门给进程分配2^10 = 1024
个连续的页框来存放它的页表。根据局部性性原理,进程在运行期间只需要访问某几个页面便可以正常运行,因此没必要让整个页表都常驻内存,浪费空间。
1.2 单级页表虚拟地址与物理地址转换
在二级页表中不过是将页号进一步华为两部分,先根据顶级页表找到页表所在位置然后调用内存,进一步访问二级页表找到物理块号,最后将物理块号与页内偏移结合得到物理地址。
1.3 二级页表结构
-
顶级页表:储存页表的基址,每个页表项指向一个下级页表。
-
页表:储存物理块号
-
页内偏移:页内具体位置偏移量
-
以32位机器,页面大小
4KB
为例,逻辑地址结构划分如下:那么与之对应页内偏移范围为[0,4095][ 顶级页号(PGD Index) | 页号(PTE Index) | 页内偏移(Offset) ]
02. 线程概念
2.1 线程基础概念
2.1.1 什么是线程?
- 在一个程序里的一个执行路线就叫做线程(
thread
)。更准确的定义是:线程是“一个进程内部的控制序列” - 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在
Linux
系统中,在CPU
眼中,看到的PCB
都要比传统的进程更加轻量化 - 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
2.1.2 线程优点
- 创建一个新线程的代价要比创建一个新进程
小
得多 - 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要
少
很多 - 线程占用的资源要比进程
少
很多 - 能充分利用多处理器的可并行数量
- 在等待慢速
I/O
操作结束的同时,程序可执行其他的计算任务 计算
密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现I/O
密集型应用,为了提高性能,将I/O
操作重叠。线程可以同时等待不同的I/O
操作。
2.1.3 线程异常和退出
单个线程出现问题,会导致整个进程崩溃。合理使用线程能够提高CPU密集型和IO密集型程序效率。
2.1.4 进程与线程区别
-
进程:资源分配的基本单位,拥有独立地址空间
-
线程:
CPU
调度的基本单位,共享进程地址空间,每个线程有独立的上下文:- 线程 ID
- 程序计数器(PC,指向当前执行的指令)
- 寄存器集合(存储当前线程的运行状态)
- 栈空间(局部变量、函数调用栈帧)
- 信号掩码、优先级等属性。
-
线程生命周期:创建、运行、阻塞、终止
2.2 线程基本操作
- 创建线程:
pthread_create
- 线程退出:
pthread_exit
(主动退出)、return
(函数结束退出)、pthread_cancel
(强制终止) - 等待线程结束:
pthread_join
(回收资源,避免线程泄漏) - 分离线程:
pthread_detach
(自动回收资源,无需等待)
2.2.1 线程创建pthread_create()
#include <pthread.h>
int pthread_create(pthread_t *thread, // 输出参数:线程IDconst pthread_attr_t *attr, // 线程属性(可NULL)void *(*start_routine)(void*), // 线程入口函数void *arg); // 传递给入口函数的参数
返回值: 成功返回0
,失败返回错误码,无errno
线程创建底层逻辑:
代码示例:
loading…
loading…