Administrator
Published on 2026-01-15 / 1 Visits
0
0

进程

程序(Program) 是静态的指令

进程是程序的一次执行

进程被创建时,操作系统会在内核空间中创建一个PCB(Process Control Block)内容包括:

1. 分配一个唯一的PID,记录所属用户的UID

2. 分配了多少内存,正在使用什么IO设备,正在使用哪些文件

3. 进程运行情况:CPU使用时间,网络流量使用情况,磁盘IO流量

PCB是进程存在的唯一标志

线程

进程帮助实现了程序的并行执行,但如果一个进程需要同时做多件事,比如qq聊天,传输文件需要同时进行, 我们可以创建多个进程,缺陷是进程的创建和销毁开销大,跨进程通信复杂;优点是一个进程崩溃不会影响其他进程。

为了提高性能,引入线程

因为只能串行的执行一系列指令,线程是CPU调度的基本单位。早期的进程(Process)其实就是现在的线程(Thread),而现代的进程则变成了一个“更高层级的资源容器”

线程的实现方式

内核级线程和用户级线程的根本区别是 管理线程的权利在进程手中还是内核手中;或者说,调度线程是否需要切换到内核态

User-level Thread

线程库(Thread Library):对于用户级线程,线程库相当于运行在用户态的操作系统,实现调度。

线程库是一个函数库,提供函数。线程池(Thread Pool) 在Thread Library基础上加入复用线程而不是销毁,是一个

一般包含:

- TCB (Thread Control Block) :类似PCB, 记录每个线程的

1. TID

2. 状态(运行、就绪、阻塞)

3. 栈指针(ESP/RSP)

4. 寄存器快照

5. 优先级

早期操作系统不支持线程,线程由线程库实现,操作系统不知道线程的存在。线程库创建,销毁和调度线程,例如:把内核分配给进程的资源分配给线程,简化逻辑如下

int main(){

1. 线程管理由进程完成

2. 线程切换没有切换到内核态

应用

P

这里调度的逻辑写在asyncio库中

优点

1. 不需要切换状态,性能高

缺点

1. 如果其中一个线程被阻塞,这个进程中其他线程都会被阻塞

Kernel-level Thread

和用户级线程的根本区别是 管理线程的权利全部在进程的主线程手中还是内核手中

1. 创建,销毁,调度由操作系统完成

2. 线程切换需要切换到内核态

场景:

- 线程执行“阻塞操作”(如 read() 磁盘文件或 recv() 网络数据) 引发Trap:

1. Trap

2. 如果需要等待数据,操作系统会把进程放入阻塞队列

3. 进程中其他不需要这个数据的线程也被阻塞了

解决方法:Non-blocking I/O 也就是协程,java中的虚拟线程。

我们使用另一种read(),当内核缓存中没有,内核不会阻塞线程,而是返回一个错误码,然后让线程继续运行。

澄清:主线程只是启动其他线程的线程,并不一定负责调度

优点

1. 一个线程被阻塞,不会影响其他线程。因为CPU调度算法下,你被阻塞会被放入阻塞队列,即使没有,也只会浪费分配给你的CPU时间片

缺点:

1. 需要变态,效率降低

import threading

import time

def worker(id):

    for i in range(3):

        # time.sleep(1) 是一个“真阻塞”操作

        # 它会告诉操作系统内核:“我没事干了,把我挂起吧”

        # 内核收到后,会主动把 CPU 切换到另一个线程

        print(f"内核级线程 {id}: 正在干活...")

        time.sleep(1) 

# 主线程逻辑

print("【主线程】: 开始创建内核级线程")

t1 = threading.Thread(target=worker, args=(1,))

t2 = threading.Thread(target=worker, args=(2,))

t1.start()

t2.start()

# 主线程调用 join(),进入阻塞状态,等待内核唤醒

t1.join()

t2.join()

print("【主线程】: 任务全部完成")

多线程模型

一对一模型

原始的KLT

多对一模型

就是原始的ULT

多对多模型

n个内核线程对应m个用户级线程


Comment