【Linux】线程概念|线程优缺点|LWP|轻量级进程|cache

Linux线程概念与应用

目录

一,线程的基本概念

二,如何重新看待进程

线程的优点

线程的缺点

线程异常

线程和进程

三,OS和用户对于"线程"的认识区别 

三,创建线程 

查看轻量级进程

证明第四个参数是传入给第三个参数 

四,进程VS线程 

cache

 五,线程的缺点 

理解LWP与TID


一,线程的基本概念

  1. 线程指的是进程中一个单一顺序的控制流(执行流),属于进程的一部分
  2. 一个进程中可以并发多个线程,每条线程并行执行不同的任务(进程:线程 = 1:n)
  3. 线程是独立调度和分派的基本单位,而进程是分配资源的基本单位
  4. 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的pcb,调用栈,自己的寄存器环境(上下文),自己的线程本地存储

总结:

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,比进程更细更轻量化(其实CPU调度的都是轻量化进程)。线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源(自己的调用栈,寄存器环境等),但线程可以共享进程所拥有的全部资源,同时线程也拥有各自私有资源(不能被其他线程共享)

创建 task_struct,且创建出来的 task_struct 和原来的 task_struct 共享进程地址空间和页表,事实上,我们所创建的其实就是三个线程,只不过这三个线程共用一张 mm_struct 和 一张页表,这三个 task_struct 就是三个不同的执行流;

总结:

在Linux中创建线程,只需创建相应的 PCB 即可,所以在Linux中,线程是在进程的内部 “运行”,线程在该进程的地址空间内 “运行”,拥有该进程的一部分资源

二,如何重新看待进程

进程 = 内核数据结构 + 进程对应的代码和数据,现在要以全新的视角看待进程:内核视角

以内核的视角看待进程:进程是承担分配系统资源的基本实体

Linux进程 = 大量的task_struct + 一个虚拟地址空间 + 页表 + 一部分的物理内存;
我们之前篇章所谈的进程 = 一个task_struct + 一个虚拟地址空间 + 页表 + 一部分的物理内存
一个进程的创建:必定要花费相应的资源;

之前所谈的进程也是承担分配系统资源的基本实体,只不过,该进程的内部只有一个执行流(一个 task_struct),而现在所谈的进程也是承担分配系统资源的基本实体,只不过,该进程内部有多个执行流(多个 task_struct),进程的内部允许只有自己一个执行流,也可以允许有多个执行流,我们之前介绍的进程,内部只有一个执行流,以前所讲的 “进程” 只是一个子集,今天所讲的进程才是全貌;

线程的优点

  • 共享资源:同一进程内的线程共享地址空间,能访问相同的全局变量,堆和文件描述符等。这种共享使得线程间通信变得更加高效。
  • 独立的执行流:每个线程都有自己的程序计数器、寄存器和栈,这使得线程可以独立执行。线程的独立性使得多个线程并行执行多个任务,提高了此程序的响应性和吞吐量。
  • 轻量级:相比于进程,线程的创建开销会小很多,且不需要分配独立的地址空间。上下文切换也比进程快,因为不涉及地址空间的切换。
  • 并发执行:在多核处理器上,不同的线程可以做到真正的并行执行

线程的缺点

  • 同步复杂性:由于共享进程空间,多个线程同时访问和修改共享数据时可能会导致数据不一致等问题
  • 性能损失:使用锁和其它同步机制会导致性能下降
  • 调试难度提高:编写和调试一个多线程程序要比单线程程序困难得多

线程异常

  • 单个线程如果出现除0或者访问野指针等问题导致线程崩溃,进程也会随着崩溃
  • 进程终止,该进程的所有线程都会终止。这也就意味着,如果某一个线程出了异常进而终止进程的话,其它的线程也都会被终止。这也是线程不安全的原因之一。

线程和进程

进程是资源分配的基本单位,而线程是调度的基本单位。
线程共享以下进程资源:

  • 代码段和数据段
  • 文件描述符表
  • 每种信号的处理方式即handler表
  • 环境变量包括当前工作目录
  • 用户id和组id

虽然线程共享进程的数据,但有属于自己的一些数据:

  • 线程ID
  • 一组寄存器:(硬件上下文的数据,线程切换时 寄存器会保存上下文数据-动态运行)
  • 栈:线程运行时,会形成各种临时变量,临时变量会被保存在各自线程的栈区
  • errno错误流
  • 信号屏蔽字
  • 调度优先级

三,OS和用户对于"线程"的认识区别 

OS 只认线程,用户也只认线程,没有轻量级进程的概念,所以Linux无法直接提供线程操作的系统调用接口,而只能给我们提供轻量级进程的接口。

但是用户只认线程,所以Linux给我们提供了一个线程库,这个库是用户级线程库,它底层是调用轻量级进程的接口的,这个线程库对这些接口进行封装,上层用户使用这个库看起来像是Linux拥有线程一样
这个线程库的名字叫 pthread,是用户级线程库
任何的Linux系统,必须要提供这个库,这个线程库是默认携带的;

这个线程库也称为原生线程库
所以用户需要关心这个线程库所提供的接口,不用关心底层的接口

三,创建线程 

 pthread_create - create a new thread(创建一个新线程)
 
 
头文件:#include <pthread.h>
 
函数原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 
参数
    第一个参数thread,代表线程ID,是一个输出型参数,pthread_t是一个无符号整数
    第二次参数attr,用于设置创建线程的属性,传入空表示使用默认属性
    第三个参数start_routine,是一个函数的地址,该参数表示新线程启动后要跳转执行的代码
    第四个参数arg,是start_routine函数的参数,用于传入
 
返回值 
成功返回0,失败返回错误码

 第三个参数说明:void *(*start_routine) (void *)

  • 该参数是一个函数指针,用于设置一个回调函数start_routine
  • 该函数的返回值是 void*,
  • 函数参数是 void*,该参数由第四个参数 arg 传入

记得添加库  

test:test.cpp
	g++ -o $@ $^ -std=c++11	-lpthread
.PHONY:clean
clean:
	rm -rf test
void* handler(void* arg)
{
    while(true)
    {
        cout<<"new thread..."<<endl;
        sleep(1);
    }
}
int main()
{

     //int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  void *(*start_routine) (void *), void *arg);
    pthread_t pid;
   int n =  pthread_create(&pid,nullptr,handler,(void*)"thread one");
   if(n!=0) //创建失败
   {
    perror("pthread_create!\n");
    return 1;
   }
    while(true)
    {
        cout<<"main thread ..."<<endl;
        sleep(1);
    }
    return 0;
}

进行运行,发现主线程和新线程在同时运行 

ps axj | head -1 && ps axj | grep test | grep -v grep

查看轻量级进程

查看轻量级线程相关信息的命令:ps -aL 

LWP(Light Weight Process)就是所谓的轻量级进程 

  • 每个轻量级进程的PID都是一样的
  •  每个轻量级进程LWP的ID都是不一样的

所以,CPU在调度的时候,是以 LWP 的ID作为唯一的标识符用来标识一个执行流的,并不是使用PID  

这就是以前写的单执行流的代码,只有一个执行流的时候,PID和LWP是等价的;

注意:信号是整体给进程发送的,不能单独发给一个 LWP 

证明第四个参数是传入给第三个参数 

 pthread_create 函数的第一个参数是一个输出型参数,返回的是线程ID,pthread_t 是一个无符号整数。

下面进行验证,该参数输出的是

typedef unsigned long int pthread_t;

 这个id十六进制地址和LWP的id是什么关系,等线程控制详细介绍

四,进程VS线程 

进程和线程切换时,消耗是不一样的;

  •  进程切换:需要切换页表 && 切换虚拟地址空间 && 切换PCB && 切换上下文数据
  • 线程切换:需要切换PCB && 切换上下文数据
  • 线程切换cache 不用更新太多,但是进程切换需要全部更新cache(主要体现在这点)

cache

cache是集成在CPU里面的,是一个硬件,是CPU很重要的组成部分,它具有数据保存的功能,它的缓存速度比寄存器慢,比内存快

 寄存器读取数据是直接在 cache 里面读取的,不是直接从内存读取,一个进程只有运行一段时间后,cache 里面才会缓存大量的热点数据;

热点数据就是:进程经常使用、经常访问、经常命中的数据(需要进程跑一段时间才会存在大量的热点数据),热点数据是被整个进程共享的;;

  • 线程切换的时候,cache内缓存的数据不用切换(线程的数据共享相同的缓存),线程需要用到新的数据直接缓存进cache即可
  • 而进程切换,cache内缓存的数据需要全部切换,新切换的进程需要重新缓存数据,这样效率就比线程慢得多了 
  • 进程切换可能导致缓存中的数据失效,因为新进程可能不会使用旧进程缓存的数据。

 五,验证线程的缺点 

 最主要的一个缺点,也是它的优点导致,因为多个线程共享内存,这也导致了如果一个线程崩溃了,该进程内的所有线程都会崩溃,新线程也会影响主线程。因为这些线程都在一个进程当中,当进程出问题时,操作系统会杀掉当前进程,释放内存;

缺乏健壮性;

注意:信号是叫做进程信号,是整体发给进程的 

理解LWP与TID

LWP本质是一个描述线程的一个概念,因为linux中没有单独为线程设计一套管理方案,又为了与进程PCB区分开(进程和线程实际上都被看作是task_struct的实例),就用LWP来表示线程。LWP和普通进程一样,由内核调度和管理。而TID是内核用来标识LWP的唯一标识符。

所有的 LWP 共享同一个进程的地址空间、打开的文件描述符、环境变量等。

<think>我们正在讨论操作系统的进程管理、线程管理和内存管理相关的问题与解决方案。根据用户提供的引用和问题,我将结构化地分析这三个核心领域的关键问题及其解决方案。 ### 一、进程管理问题与解决方案 #### 问题1:进程崩溃影响其他进程 - **问题描述**:传统系统中,一个进程崩溃可能导致整个系统崩溃(如早期Windows 9x)。现代操作系统通过虚拟地址空间隔离进程[^1]。 - **解决方案**: - **虚拟内存机制**:每个进程拥有独立的虚拟地址空间,物理内存通过MMU映射隔离 - **进程控制块(PCB)**:保存进程状态(运行/就绪/阻塞),崩溃时仅回收自身资源[^2] - **信号处理**:Linux通过SIGSEGV等信号捕获崩溃,由init进程接管孤儿进程 #### 问题2:进程创建/销毁开销大 - **问题描述**:进程创建需分配内存、加载程序、建立PCB等,典型耗时约100ms[^4] - **解决方案**: - **写时复制(Copy-On-Write)**:fork()时共享父进程内存,仅在实际修改时复制 - **进程池**:预创建进程(如Apache Worker Pool),避免运行时创建开销 - **轻量级进程(LWP)**:Solaris实现,共享部分内核资源 #### 问题3:僵尸进程与孤儿进程 - **僵尸进程**:子进程退出但父进程未调用wait(),PCB残留内核中 ```bash # 示例:Linux中查找僵尸进程 ps aux | grep 'Z' ``` - **解决方案**: - 父进程显式调用wait()/waitpid() - 父进程捕获SIGCHLD信号处理 - 孤儿进程由init进程(PID=1)自动接管回收[^2] --- ### 二、线程管理问题与解决方案 #### 问题1:线程同步问题 - **竞态条件**:多线程访问共享数据导致结果不确定性 - **解决方案**: | 机制 | 原理 | 适用场景 | |--------------|----------------------------------------------------------------------|------------------------| | **互斥锁** | pthread_mutex_lock()保证临界区独占访问 | 共享数据结构更新 | | **信号量** | 通过PV操作控制资源计数(如生产者-消费者模型) | 线程池任务调度 | | **条件变量** | pthread_cond_wait()实现事件驱动协作 | 异步任务通知 | | **原子操作** | CAS(Compare-And-Swap)指令实现无锁编程 | 高性能计数器 | #### 问题2:线程调度开销 - **问题描述**:线程上下文切换需保存寄存器、栈指针等,典型开销1-10μs - **解决方案**: - **线程本地存储(TLS)**:减少共享数据依赖 - **用户态线程**:Go语言的goroutine,切换由runtime管理不经过内核 - **绑核(Affinity)**:将线程固定到CPU核心,减少缓存失效 #### 问题3:死锁问题 - **必要条件**:互斥、持有并等待、非抢占、循环等待 - **解决方案**: 1. **预防策略**:破坏死锁条件,如全资源一次性申请(银行家算法) 2. **检测与恢复**:定期构建资源分配图检测环,强制终止进程 3. **避免策略**:Dijkstra银行家算法,安全状态判断公式: $$ \text{Max}[i][j] \leq \text{Available}[j] + \sum \text{Allocation}[k][j] $$ 其中$i$为进程,$j$为资源类型 --- ### 三、内存管理问题与解决方案 #### 问题1:内存碎片化 - **外部碎片**:空闲内存分散在多个不连续区域 - **解决方案**: - **伙伴系统**:Linux将内存按$2^n$大小分块,合并时要求相邻且同大小 - **Slab分配器**:内核对象(如task_struct)专用缓存,避免小块碎片 - **虚拟内存压缩**:移动已分配页面形成连续空间(ZRAM机制) #### 问题2:内存不足(OOM) - **问题描述**:进程申请内存超过物理内存+Swap空间 - **解决方案**: - **页面置换算法**: | 算法 | 特点 | 实现复杂度 | |-------------|----------------------------------------------------------------------|------------| | **OPT** | 理论最优(置换未来最晚使用的页) | 不可实现 | | **LRU** | 最近最少使用(时间局部性原理) | 中等 | | **Clock** | 近似LRU,使用访问位循环检测 | 简单 | - **OOM Killer**:Linux内核选择`badness`评分高的进程终止,评分公式: $$ \text{Score} = \frac{\text{占用内存} \times 10}{\sqrt{\text{运行时间}}} $$ #### 问题3:内存泄漏 - **问题描述**:进程持续申请内存未释放,如C/C++中malloc后未free - **检测工具**: - Valgrind:监测未释放堆内存 ```shell valgrind --leak-check=yes ./program ``` - GDB调试:观察进程RSS持续增长 - **解决方案**: - 智能指针(C++的shared_ptr/unique_ptr) - 垃圾回收机制(Java/Python GC) --- ### 关键机制关联分析 | 管理领域 | 核心机制 | 数学描述/公式 | 性能影响 | |--------------|---------------------------|-----------------------------------|------------------------| | **进程调度** | 多级反馈队列 | 响应时间$R = T_w + T_s$ | 平衡吞吐量与响应延迟 | | **内存映射** | 页表转换 | $物理地址 = \text{PTE} \times \text{PAGE\_SIZE} + \text{偏移量}$ | TLB命中率决定访问速度 | | **线程同步** | 自旋锁 | 忙等待循环次数$n \propto \frac{1}{\text{CPU频率}}$ | 临界区短时高效 | > **案例**:Chrome浏览器进程模型 > 采用多进程架构(渲染进程、插件进程分离),结合: > - 进程隔离:单个页面崩溃不影响整个浏览器[^1] > - 线程池:网络模块使用线程池处理并发请求(避免频繁创建线程)[^4] §§ 相关问题 §§ 1. 如何设计一个避免僵尸进程产生的进程监控系统? 2. 在多核CPU环境下,线程绑核策略如何优化缓存命中率? 3. 虚拟内存系统中LRU算法的实现代价如何平衡? 4. 用户态线程与内核态线程的混合调度机制有哪些? 5. 内存压缩技术(ZRAM)在移动设备中的应用原理是什么?</think>### 操作系统核心管理问题与解决方案 #### 一、进程管理 **问题1:进程崩溃影响范围** - **问题**:传统系统中进程崩溃可能影响整个系统 - **解决方案**: 1. **虚拟地址空间隔离**:每个进程拥有独立虚拟地址空间(如Linux的mm_struct)[^1] 2. **进程控制块(PCB)**:崩溃时内核仅回收自身资源(文件描述符、内存等)[^2] 3. **信号机制**:通过SIGKILL/SIGTERM终止进程而不影响其他进程 **问题2:进程创建/销毁开销大** - **问题**:进程创建需分配资源(典型开销>100ms)[^4] - **解决方案**: ```c // 写时复制(Copy-On-Write)示例 pid_t pid = fork(); // 实际内存复制延迟到写入时发生 if (pid == 0) { // 子进程修改数据时才复制物理页 modify_data(); } ``` 1. **进程池技术**:预创建进程复用(如Apache Worker Pool) 2. **轻量级进程(LWP)**:Solaris/ Linux的clone()系统调用 **问题3:僵尸进程与孤儿进程** - **僵尸进程**:子进程退出但父进程未wait(),PCB残留内核 ```bash # 解决方案:父进程中处理SIGCHLD信号 signal(SIGCHLD, SIG_IGN); // 显式忽略信号,内核自动回收 ``` - **孤儿进程**:父进程先退出,由init进程(PID=1)接管[^2] --- #### 二、线程管理 **问题1:线程同步冲突** - **问题**:多线程访问共享数据导致竞态条件 - **解决方案**: | 同步机制 | 适用场景 | 示例 | |----------------|-------------------------|--------------------------| | 互斥锁 | 共享资源独占访问 | pthread_mutex_lock() | | 信号量 | 资源计数控制 | sem_wait()/sem_post() | | 条件变量 | 事件驱动协作 | pthread_cond_wait() | **问题2:线程调度开销** - **问题**:频繁线程切换消耗CPU(上下文切换开销1-10μs) - **解决方案**: 1. **线程池模式**:固定线程数处理任务(如Java Executor) 2. **用户态线程**:Go的goroutine,切换不经过内核 3. **绑核(Affinity)**:将线程固定到特定CPU核心 **问题3:死锁预防** - **死锁条件**:互斥、持有并等待、非抢占、循环等待 - **解决方案**: $$ \text{银行家算法:} \forall i, \text{ Available} \geq \text{Need}_i \rightarrow \text{安全状态} $$ 1. 有序资源分配法(打破循环等待) 2. 超时回滚机制 --- #### 三、内存管理 **问题1:内存碎片化** - **外部碎片**:空闲内存分散无法利用 - **解决方案**: 1. **分页机制**:物理内存按固定大小划分(通常4KB) $$ \text{物理地址} = \text{页框号} \times \text{PAGE\_SIZE} + \text{偏移量} $$ 2. **Slab分配器**:内核对象专用缓存(如Linux的kmem_cache) **问题2:内存不足(OOM)** - **问题**:物理内存+Swap空间耗尽 - **解决方案**: 1. **页面置换算法**: | 算法 | 特点 | 实现复杂度 | |--------|--------------------------|------------| | LRU | 最近最少使用 || | Clock | 近似LRU(访问位轮询) || 2. **OOM Killer**:Linux选择`badness`高的进程终止: $$ \text{badness} = \frac{\text{内存占用}^{10}}{\text{运行时间}} $$ **问题3:进程挂起与激活** - **场景**:内存不足时挂起阻塞态进程[^3] - **激活策略**: 1. 优先激活等待事件即将发生的进程 2. 使用二级存储(Swap分区)保存挂起进程镜像 --- ### 关键机制关联 | 管理领域 | 核心机制 | 性能影响 | |--------------|---------------------------|------------------------| | **进程-内存**| 写时复制(COW) | 减少90%进程创建开销 | | **线程-进程**| 线程共享进程资源 | 上下文切换快5-10倍 | | **内存-调度**| 页表缓存(TLB) | 地址转换提速100倍 | > **案例**:Chrome浏览器进程模型 > - 每个标签页独立进程:崩溃不影响整体(隔离性)[^1] > - 渲染线程与GPU线程分离:避免界面卡死(线程调度优化)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值