线程概念与控制
目录
从概念角度理性理解线程
一、从教材角度看进程与线程
二、从内核和资源的角度看进程和线程
三、进程与线程的关系
四、结论
从理性角度理解资源划分
一、页表
二、相关概念深度理解
线程的深刻理解
一、线程关于资源操作的实质
二、线程的优点
三、线程的缺点
四、适合使用线程的场景和不适合使用线程的场景
进程 VS 线程
一、定义与基本概念
二、资源分配
三、创建与销毁开销
四、调度与切换
五、通信机制
六、稳定性与安全性
七、 优缺点总结
八、总结
Linux下的线程控制
一、pthread线程库
二、接口
从概念角度理性理解线程
一、从教材角度看进程与线程
1.进程=内核数据结构+代码和数据(执行流)
2.线程是进程内部的一个执行分支(执行流)
注1:执行流<=进程
注2:执行流是程序执行过程中一条独立的指令序列,在Linux中,执行流通常指线程,两者是同一概念的不同表述
二、从内核和资源的角度看进程和线程
1.进程是承担分配系统资源的基本单位
2.线程是CPU调度的基本单位
三、进程与线程的关系
创建进程通过共享“窗口”(地址空间)将资源分配给不同的task_struct,所以可以用进程模拟出来线程
四、结论
1.Linux下线程可以用进程模拟
2.对资源的划分本质上是对地址空间虚拟地址范围的划分
3.函数是虚拟地址(逻辑地址)空间的集合,代码块的划分是让线程未来执行ELF程序的不同函数
4.进程可以是内部只有一个线程的进程
5.在Linux中,线程是轻量级进程(LWP,Lightweight Process),由内核的clone()
系统调用实现
6.进程强调独占,部分资源共享
7.线程强调共享,部分资源独占,比如线程的上下文数据(这说明线程是独立调度的)和栈(说明线程是独立的)
从理性角度理解资源划分
一、页表
注:页表不是单张页表
1.页表作用:在CPU内拿到虚拟地址,通过页表找出与该虚拟地址相对应的物理地址,从而进行数据获取
2.虚拟地址到物理地址的转换具体过程(以32位为例)
<1>虚拟地址到物理地址的转换由MMU硬件自动完成
第一阶段:先查找到虚拟地址对应的页框
第二阶段:根据虚拟地址做为页内偏移量访问具体字节
<2>以上页表形式只是一种情况,为了提高效率有引入了TLB(快表),现在快表内查找,若是找不到再到页表内查找
3.细节
<1>向页表内添加内容的步骤:
申请内存-->查找数组-->找到未被使用的page-->确定page index-->找到物理页框地址
<2>写时拷贝、缺页中断、内存申请等背后可能都要重新建立页表和建立映射关系的操作
<3>进程是由一张页目录和n张页表构建的映射关系,其中虚拟地址是索引,物理地址页框是目标
物理地址=页框地址+虚拟地址(低12位)
<4>为什么是虚拟地址的低12位作为页内偏移量
低:保证很好的聚集效果
12位:因为页框大小为4KB,12个比特位刚好可以表示一行内所有情况
二、相关概念深度理解
1.资源划分本质上就是对地址空间的划分
2.资源共享本质上就是虚拟地址的共享
3.执行流看到的资源本质上是在合法情况下,拥有的虚拟地址(可以认为虚拟地址就是资源的代表)
4.页表的本质就是一张从虚拟到物理的地图
线程的深刻理解
一、线程关于资源操作的实质
1.线程进行资源划分的本质是划分地址空间,再本质是划分页表
2.线程进行资源共享的本质就是对地址空间的共享,再本质是对页表条目的共享
二、线程的优点
1.提高程序响应速度
<1>并行执行:线程允许程序在单核或多核 CPU 上同时执行多个任务。例如,在图形用户界面(GUI)程序中,主线程负责处理用户交互,而后台线程可以执行耗时的计算或 I/O 操作,避免界面卡顿。
<2>实时性:在实时系统中,线程可以确保关键任务及时响应,例如在嵌入式系统中,传感器数据采集和用户输入处理可以并行进行。
2.资源共享
<1>内存共享:同一进程中的线程共享相同的内存空间,包括全局变量、堆内存和打开的文件描述符。这使得线程间通信更加高效,避免了进程间通信(IPC)的开销。
<2>数据一致性:线程可以方便地访问和修改共享数据,但需要注意同步问题(如使用锁或信号量)以避免数据竞争。
3.降低系统开销
<1>轻量级:线程的创建和销毁开销远小于进程。进程需要独立的地址空间和资源,而线程只需要栈空间和寄存器上下文。
<2>上下文切换快:线程切换只需要保存和恢复少量寄存器,而进程切换需要保存和恢复整个地址空间,因此线程切换速度更快。
4.简化编程模型
<1>任务分解:线程可以将复杂的任务分解为多个子任务,每个子任务由一个线程执行。例如,在 Web 服务器中,每个客户端请求可以由一个独立的线程处理。
<2>模块化设计:线程使得程序结构更加清晰,便于维护和扩展。
三、线程的缺点
1.同步问题
<1>数据竞争:多个线程同时访问和修改共享数据时,可能导致数据不一致。例如,两个线程同时对一个全局变量进行加法操作,可能导致结果错误。
<2>死锁:线程在等待锁时可能陷入死锁状态。例如,线程 A 持有锁 1 并等待锁 2,而线程 B 持有锁 2 并等待锁 1,导致两个线程都无法继续执行。
2.调试困难
3.资源竞争
<1>CPU 争用:如果线程数量过多,可能导致 CPU 资源争用,降低系统性能。
<2>内存消耗:虽然线程本身比进程轻量,但大量线程仍然会消耗系统资源,如栈空间和内核调度开销。
4.平台依赖性
四、适合使用线程的场景和不适合使用线程的场景
1.适合使用线程的场景
<1>需要并行执行任务以提高性能,如科学计算、图像处理。
<2>需要实时响应的事件驱动程序,如游戏、多媒体应用。
<3>需要共享大量数据的程序,如数据库系统。
2.不适合使用线程的场景
<1>需要高度隔离的任务,例如不同用户之间的进程。
<2>对安全性要求极高的应用,如银行系统,进程隔离可以提供更好的安全性
进程 VS 线程
一、定义与基本概念
1.进程:进程是程序在操作系统中的一次执行实例,是资源分配的基本单位。每个进程拥有独立的地址空间、文件描述符、全局变量等资源。
2线程:线程是进程中的一个执行单元,是 CPU 调度的基本单位。线程共享进程的资源(如地址空间、文件描述符),但拥有独立的栈空间和程序计数器。
二、资源分配
维度 | 进程 | 线程 |
---|---|---|
地址空间 | 每个进程有独立的地址空间 | 同一进程中的线程共享地址空间 |
文件描述符 | 每个进程有独立的文件描述符表 | 同一进程中的线程共享文件描述符表 |
全局变量 | 每个进程有独立的全局变量 | 同一进程中的线程共享全局变量 |
栈空间 | 每个进程有独立的栈空间 | 每个线程有独立的栈空间 |
三、创建与销毁开销
1.进程:创建和销毁进程需要分配或释放独立的地址空间、文件描述符等资源,开销较大。
2.线程:线程的创建和销毁只需分配或释放栈空间和寄存器上下文,开销较小。
四、调度与切换
1.进程:进程切换需要保存和恢复整个地址空间,开销较大,切换速度较慢。
2.线程:线程切换只需保存和恢复少量寄存器,开销较小,切换速度较快。
五、通信机制
1.进程:进程间通信(IPC)需要使用管道、消息队列、共享内存等机制,通信复杂且开销较大。
2.线程:线程间通信可以直接访问共享数据,通信简单且高效,但需要同步机制(如锁、信号量)避免数据竞争。
六、稳定性与安全性
1.进程:进程之间相互独立,一个进程崩溃不会影响其他进程,稳定性较高。
2.线程:同一进程中的线程共享资源,一个线程崩溃可能导致整个进程崩溃,稳定性较低。
七、 优缺点总结
1、进程的优缺点
<1>优点:
稳定性高,一个进程崩溃不会影响其他进程。
安全性好,进程间资源隔离。
<2>缺点:
创建和切换开销大。
进程间通信复杂。
2、线程的优缺点
<1>优点:
创建和切换开销小。
线程间通信简单高效
<2>缺点:
稳定性低,一个线程崩溃可能导致整个进程崩溃。
需要复杂的同步机制避免数据竞争。
八、总结
特性 | 进程 | 线程 |
---|---|---|
资源分配 | 独立地址空间、文件描述符 | 共享地址空间、文件描述符 |
创建开销 | 大 | 小 |
切换开销 | 大 | 小 |
通信机制 | 复杂(IPC) | 简单(共享数据) |
稳定性 | 高 | 低 |
适用场景 | 需要隔离和安全性的场景 | 需要高效并发和资源共享的场景 |
Linux下的线程控制
一、pthread线程库
1.pthread线程库是第三方库,使用时需要链接,它不属于系统调用
2.Linux系统只存在轻量级进程,Linux的线程是在用户层实现的
二、接口
1.int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
<1>头文件:<pthread.h>
<2>参数:
pthread_t *thread
:- 指向一个
pthread_t
类型的变量的指针,用于存储新创建线程的标识符。
- 指向一个
const pthread_attr_t *attr
:- 指向一个
pthread_attr_t
类型的变量的指针,用于指定线程的属性。如果传入NULL
,则使用默认属性。
- 指向一个
void *(*start_routine) (void *)
:- 线程启动后要执行的函数的指针。该函数必须符合特定的签名,即接受一个
void*
类型的参数并返回一个void*
类型的值。
- 线程启动后要执行的函数的指针。该函数必须符合特定的签名,即接受一个
void *arg
:- 传递给
start_routine
函数的参数。
- 传递给
<3>返回值:成功返回0;失败返回错误码
<4>功能:创建新线程的 POSIX 线程,它允许在多线程编程中生成新的执行流,从而可以并发地执行代码。
2.int pthread_join(pthread_t thread, void **retval);
<1>头文件:<pthread.h>
<2>参数:
pthread_t thread
:- 要等待的线程的标识符。该标识符是由
pthread_create
创建线程时返回的。
- 要等待的线程的标识符。该标识符是由
void **retval
:- 一个指向指针的指针,用于存储被等待线程的返回值。如果不需要获取返回值,可以传入
NULL
- 一个指向指针的指针,用于存储被等待线程的返回值。如果不需要获取返回值,可以传入
<3>返回值:成功返回0;失败返回错误码
<4>功能:用于等待线程终止的 POSIX 线程,它允许主线程或其他线程等待指定的线程完成执行,从而确保在线程完成后再进行某些操作或回收线程资源。
注:join是基于线程健康跑完的情况下,不需要处理异常情况,因为若线程崩溃,整个进程都崩溃了,此时join无意义
3.int pthread_detach(pthread_t thread);
<1>头文件:<pthread.h>
<2>参数:thread
:要分离的线程标识符。
<3>返回值:成功返回0;失败返回错误码
<4>功能:分离一个线程,使其在结束时自动释放资源
4.void pthread_exit(void *retval);
<1>头文件:<pthread.h>
<2>参数:retval
:线程的返回值
<3>功能:终止当前线程
5.int pthread_cancel(pthread_t thread);
<1>头文件:<pthread.h>
<2>参数:thread
:要取消的线程标识符。
<3>返回值:成功返回0;失败返回错误码
<4>功能:取消指定线程的执行。
6.pthread_t pthread_self(void);
<1>头文件:<pthread.h>
<2>返回值:当前线程的标识符。
<3>功能:获取当前线程的标识符