注意:本文章整理于GPT。
27,29
1、操作系统知识相关。进程 线程 协程的区别?进程和线程、上下文切换了什么?共享了什么?线程独占什么?切换时候内核做了什么?
进程、线程、协程的区别
1. 进程(Process)
- 定义:进程是一个程序在其自身地址空间中的执行实例,是资源分配的最小单位。
- 特性:
- 独立性:每个进程都有自己独立的内存空间(代码段、数据段、堆、栈)。
- 资源拥有:进程拥有自己的资源(文件句柄、信号等)。
- 调度:操作系统负责调度和管理进程。
2. 线程(Thread)
- 定义:线程是进程内的一个执行单元,是调度的最小单位。一个进程可以有多个线程,共享该进程的资源。
- 特性:
- 共享资源:同一进程内的线程共享进程的资源(内存、文件句柄等)。
- 独立栈:每个线程都有自己的栈空间和寄存器上下文。
- 轻量级:创建和销毁线程的开销比进程小,线程切换速度快。
3. 协程(Coroutine)
- 定义:协程是一种用户态的轻量级线程,由用户程序来调度,而不是操作系统内核。
- 特性:
- 用户态调度:协程的切换由程序代码控制,不需要操作系统的上下文切换。
- 高效性:由于不涉及内核切换,协程切换非常快,适用于I/O密集型任务。
- 协作式多任务:协程通过显式的让出操作(如
yield
)来实现多任务。
上下文切换和共享内容
进程上下文切换
-
切换内容:
- 处理器状态:保存和恢复CPU寄存器(通用寄存器、程序计数器、堆栈指针等)。
- 内存管理信息:切换页表或段表,重新设置内存管理单元(MMU)。
- 内核态数据:切换内核栈、任务控制块(TCB)、进程描述符等。
-
共享内容:
- 几乎没有共享:进程间通常不共享内存空间,但可以通过IPC(进程间通信)共享数据,如管道、消息队列、共享内存等。
线程上下文切换
-
切换内容:
- 处理器状态:保存和恢复CPU寄存器。
- 线程相关数据:切换线程栈、线程局部存储(TLS)、线程控制块(TCB)等。
-
共享内容:
- 进程资源:同一进程内的线程共享进程的虚拟内存空间、全局变量、文件句柄等。
线程独占内容
- 独占栈:每个线程有自己独立的栈空间。
- 独占寄存器上下文:每个线程在运行时都有自己独立的CPU寄存器状态。
- 线程本地存储(TLS):线程拥有自己的局部存储区域,用于存放线程特定的数据。
上下文切换时内核做了什么
- 保存当前线程/进程状态:保存当前线程或进程的CPU寄存器状态到其内核栈中。
- 选择下一个要运行的线程/进程:根据调度算法选择下一个要运行的线程或进程。
- 恢复下一个线程/进程状态:从内核栈中恢复下一个线程或进程的CPU寄存器状态。
- 更新内存管理单元(MMU):如果是进程切换,需要更新页表或段表,以映射新进程的地址空间。
- 内核数据结构更新:更新内核中的调度队列、统计信息等。
这种切换涉及的系统调用通常有context_switch
、schedule
等,对于用户态来说,上下文切换是透明的,由操作系统内核完全管理。
2、进程上下文切换开销?进程的内存空间?一个可执行文件如何变成进程的?CPU调度基本单位?进程和线程的优缺点,各适用于什么场合?
进程上下文切换开销
1. 上下文切换的组成部分
- 保存当前状态:将当前进程的CPU寄存器、程序计数器等状态保存到该进程的内核栈中。
- 切换页表:在不同进程之间切换时,更新MMU中的页表或段表以切换进程的地址空间。
- 内核数据结构:更新内核调度队列和其他相关数据结构。
- 恢复新状态:从下一个要运行的进程的内核栈中恢复其CPU寄存器和程序计数器状态。
2. 开销因素
- CPU寄存器状态保存和恢复:涉及多个寄存器的保存和恢复操作。
- 页表切换:需要刷新TLB(Translation Lookaside Buffer),增加内存访问延迟。
- 缓存失效:新进程可能带来缓存失效,导致缓存命中率降低,增加内存访问时间。
3. 开销大小
- 上下文切换开销取决于CPU架构和操作系统实现,但通常来说,进程切换的开销较高,可能需要数百到数千个CPU周期。
进程的内存空间
1. 内存布局
- 代码段(Text Segment):存放程序的可执行代码。
- 数据段(Data Segment):存放已初始化的全局变量和静态变量。
- BSS段(Block Started by Symbol):存放未初始化的全局变量和静态变量。
- 堆(Heap):动态内存分配,由
malloc
等函数分配,向高地址方向增长。 - 栈(Stack):用于函数调用时存储局部变量、返回地址等,向低地址方向增长。
一个可执行文件如何变成进程的?
1. 加载可执行文件
- 文件读取:操作系统读取可执行文件(如ELF格式)到内存中。
- 内存映射:将文件的各个段(代码段、数据段等)映射到进程的虚拟地址空间中。
- 堆和栈初始化:为堆和栈分配初始内存。
2. 创建进程控制块(PCB)
- 进程表项:在操作系统的进程表中创建新的进程表项(PCB),包含进程ID、状态、寄存器状态等信息。
3. 初始化CPU状态
- 寄存器设置:设置程序计数器(PC)指向程序的入口点,初始化其他CPU寄存器。
4. 开始执行
- 调度:将新进程加入调度队列,等待CPU调度运行。
CPU调度基本单位
- 线程:在现代操作系统中,CPU调度的基本单位通常是线程(Thread)。线程比进程更轻量,具有更高的调度效率。
进程和线程的优缺点及适用场合
进程(Process)
优点
- 独立性强:进程之间互不干扰,提高了系统稳定性和安全性。
- 资源隔离:各进程拥有独立的地址空间,减少了数据竞争和资源冲突。
缺点
- 开销大:进程创建、销毁和上下文切换开销较大。
- 通信复杂:进程间通信(IPC)复杂且效率低。
适用场合
- 高可靠性需求:如操作系统服务、数据库管理系统。
- 独立任务:需要独立运行的任务,如服务器进程、后台任务。
线程(Thread)
优点
- 轻量级:线程创建、销毁和上下文切换开销较小。
- 共享内存:线程共享进程的内存空间,数据传递和同步较快。
缺点
- 数据同步复杂:共享内存容易引发数据竞争和死锁问题,需要精细的同步机制。
- 资源争用:线程共享进程资源,错误的资源操作可能导致整个进程崩溃。
适用场合
- 高并发需求:如网络服务器、高性能计算。
- 实时性要求:如图形界面更新、多媒体应用。
总结
- 进程适用于需要高可靠性和独立性的场合,如操作系统服务和后台任务。
- 线程适用于需要高并发和实时性的场合,如网络服务器和图形界面更新。
3、fork的作用是什么?为什么不用system?内核创建进程时会自动打开哪几个文件?进程控制块?什么情况下需要多线程频繁切换?僵尸进程是什么,怎么处理?
fork
的作用和与 system
的比较
fork
的作用
- 创建新进程:
fork
是系统调用,用于创建一个新的进程。新进程称为子进程,它是从父进程复制而来的。 - 独立运行:子进程从
fork
调用返回时开始执行,独立于父进程运行。子进程与父进程共享文件描述符,但具有独立的地址空间。 - 并行执行:
fork
允许父进程和子进程并行执行,从而实现并发。
为什么不用 system
system
的作用:system
函数用于执行一个 shell 命令,在调用时会创建一个新的进程来执行该命令,然后等待命令执行完成。- 灵活性:
fork
允许在子进程中执行任意代码,而system
只能执行特定的 shell 命令。 - 安全性:使用
system
时需要传递一个字符串,该字符串会被 shell 解释执行,可能带来安全隐患(如命令注入)。 - 控制权:
fork
后可以通过exec
系列函数执行新程序,且在执行前可以设置子进程的环境和状态;system
则不提供这种控制。
内核创建进程时会自动打开哪几个文件?
当内核创建一个新进程时,会自动为该进程打开以下几个标准文件描述符:
- 标准输入(stdin):文件描述符0,通常连接到终端的输入。
- 标准输出(stdout):文件描述符1,通常连接到终端的输出。
- 标准错误(stderr):文件描述符2,通常连接到终端的错误输出。
这些文件描述符在父进程和子进程之间是共享的。
进程控制块(PCB)
- 定义:进程控制块(Process Control Block, PCB)是操作系统内核中用来管理进程的一个数据结构。
- 包含信息:
- 进程标识符(PID):进程的唯一标识符。
- 进程状态:进程的当前状态(如运行、就绪、等待)。
- 寄存器状态:CPU寄存器的内容,包括程序计数器和栈指针。
- 内存管理信息:指向进程页表或段表的指针。
- 文件描述符表:进程打开的文件列表。
- 调度信息:优先级、调度队列指针等。
- 会计信息:CPU使用时间、进程创建时间等。
什么情况下需要多线程频繁切换?
- I/O密集型任务:在等待I/O操作(如磁盘读写、网络通信)时,可以切换到其他线程执行,以提高资源利用率。
- 实时性要求:在实时系统中,需要频繁切换线程以保证及时响应各种外部事件。
- 多任务并发:在多任务系统中,需要频繁切换线程以实现并发执行多个任务。
僵尸进程是什么,怎么处理?
僵尸进程的定义
- 僵尸进程:僵尸进程是指已经终止但其父进程尚未调用
wait
或waitpid
系统调用获取其退出状态的进程。僵尸进程仍然占据进程表项(PID),但不占用系统资源。
处理僵尸进程
- 父进程调用
wait
或waitpid
:父进程应调用wait
或waitpid
以回收子进程的退出状态,从而释放其PID和进程表项。 - 信号处理:父进程可以通过捕捉
SIGCHLD
信号,在信号处理程序中调用wait
或waitpid
来处理子进程退出。 - 孤儿进程处理:如果父进程先于子进程退出,子进程会成为孤儿进程,由init进程(PID为1)接管。init进程会自动调用
wait
回收孤儿进程的退出状态,从而防止僵尸进程的产生。
总结
fork
用于创建新进程,提供灵活的并行执行机制;而system
仅用于执行简单的 shell 命令,灵活性和安全性较低。- 内核在创建进程时自动打开标准输入、输出和错误文件描述符。
- 进程控制块包含进程标识、状态、内存管理信息等,是操作系统管理进程的核心数据结构。
- 需要多线程频繁切换的场合包括I/O密集型任务、实时系统和多任务并发。
- 僵尸进程是已终止但未被父进程回收的进程,处理方法包括父进程调用
wait
系列函数或信号处理。
4、threadlocal的实现原理,业务用来做什么?程序什么时候应该使用线程,什么时候单线程效率高?cpu怎么调度进程?调度算法?多线程好处?
ThreadLocal
的实现原理及业务用途
ThreadLocal
的实现原理
ThreadLocal
是 Java 提供的一种机制,允许每个线程都拥有其私有的变量副本。主要实现原理包括:
- 内部类
ThreadLocalMap
:每个Thread
对象都有一个ThreadLocalMap
实例,用于存储ThreadLocal
变量。 ThreadLocalMap
的键值对:ThreadLocalMap
以ThreadLocal
对象作为键,线程特定的值作为值,存储每个线程独有的变量。get
和set
方法:get
方法:从当前线程的ThreadLocalMap
中获取与当前ThreadLocal
对象关联的值。如果没有关联的值,则调用initialValue
方法返回初始值,并将其存储在ThreadLocalMap
中。set
方法:将当前线程的ThreadLocalMap
中与当前ThreadLocal
对象关联的值设置为新值。
业务用途
- 线程安全:在并发编程中,使用
ThreadLocal
可以避免线程间共享变量的竞争问题,从而简化线程安全的实现。 - 会话管理:在Web应用中,使用
ThreadLocal
存储与用户会话相关的数据(如用户ID),方便在不同层之间传递而无需显式传递参数。 - 事务管理:在数据库操作中,使用
ThreadLocal
存储事务对象,确保同一线程内的数据库操作在同一事务中执行。
程序何时应该使用线程,何时单线程效率高?
使用多线程的场景
- I/O 密集型任务:如网络通信、文件读写等。多线程可以在等待I/O操作完成时切换执行其他任务,提高资源利用率。
- 并行处理:需要同时处理多个独立任务时,如并行计算、Web服务器处理多个请求。
- 实时性要求:需要快速响应外部事件,如图形界面应用、实时数据处理。
使用单线程的场景
- CPU 密集型任务:如复杂的计算任务。如果任务本身是串行的,并且CPU是主要瓶颈,单线程避免了线程切换开销。
- 任务简单且短暂:如简单的数据处理或一次性任务,使用多线程可能引入不必要的复杂性和上下文切换开销。
- 共享资源访问:当任务需要频繁访问和修改共享资源,单线程可以避免复杂的同步机制和数据竞争问题。
CPU如何调度进程?调度算法?
CPU 调度过程
- 选择进程:操作系统根据调度算法选择下一个要运行的进程或线程。
- 上下文切换:保存当前进程的状态(寄存器、程序计数器等)到进程控制块(PCB),然后从选定进程的PCB中恢复其状态。
- 更新调度队列:将当前进程移出运行队列,并将选定进程移入运行队列。
调度算法
- 先来先服务(FCFS):按进程到达的顺序调度,简单但可能导致长时间等待。
- 短作业优先(SJF):优先调度执行时间最短的进程,最小化平均等待时间,但需要知道作业长度。
- 轮转法(Round Robin):每个进程按时间片轮流执行,适用于时间共享系统。
- 优先级调度:根据进程优先级调度,优先级高的进程优先执行。可能导致低优先级进程饥饿。
- 多级队列调度:将进程分成多个队列,每个队列有不同的调度算法,如交互式进程和批处理进程分开调度。
多线程的好处
- 并行处理:多线程可以利用多核CPU的并行处理能力,显著提高计算任务的执行速度。
- 资源利用率高:在I/O密集型任务中,多线程可以在等待I/O操作时执行其他任务,提高资源利用率。
- 响应性好:在图形界面应用中,使用多线程可以保持界面响应,同时在后台执行耗时任务。
- 结构清晰:多线程将不同任务分离到不同的线程中,提高程序结构的清晰度和可维护性。
总结
ThreadLocal
提供线程局部变量,确保每个线程都有独立的变量副本,常用于线程安全、会话管理和事务管理。- 使用多线程适用于I/O密集型任务、并行处理和实时性要求的场景;单线程适用于CPU密集型任务、简单短暂任务和共享资源访问频繁的场景。
- CPU调度进程通过选择进程、上下文切换和更新调度队列来实现,常见调度算法包括FCFS、SJF、Round Robin、优先级调度和多级队列调度。
- 多线程的好处包括并行处理、资源利用率高、响应性好和程序结构清晰。
5、进程怎么实现访问隔离?线程有什么数据是自己私有,哪些是共享的?io密集型和计算密集型分别适合多线程还是多进程?如何理解协程不被操作系统内核管理,而完全是由程序控制?用户级线程和内核级线程的区别?
进程如何实现访问隔离?
进程的访问隔离通过以下几种机制实现:
-
独立地址空间:
- 每个进程拥有独立的虚拟地址空间,进程的内存分布在独立的代码段、数据段、堆和栈中。
- 使用硬件支持的内存管理单元(MMU),通过页表或段表将虚拟地址映射到物理内存,确保进程之间的内存访问隔离。
-
进程控制块(PCB):
- 操作系统维护每个进程的控制块(PCB),其中包含进程的状态、寄存器信息、打开的文件描述符等。
- PCB之间相互独立,进程间无法直接访问彼此的控制块。
-
权限和安全机制:
- 操作系统通过用户态和内核态的特权级别控制,防止进程非法访问内核数据或其他进程数据。
- 使用系统调用和保护模式来限制进程的操作范围。
线程的数据私有与共享
私有数据:
- 栈:每个线程都有自己的栈空间,用于存储函数调用、局部变量、返回地址等。
- 寄存器:包括程序计数器、栈指针和通用寄存器等,在线程切换时保存和恢复。
共享数据:
- 全局变量和静态变量:位于进程的全局数据段和静态数据段,所有线程共享。
- 堆:通过动态内存分配(如
malloc
)获取的内存区域,所有线程共享。 - 打开的文件描述符:所有线程共享进程打开的文件描述符表,可以共同操作文件。
IO密集型和计算密集型任务适合的多线程与多进程模型
IO密集型任务:
- 多线程:适合IO密集型任务,因为线程的上下文切换开销较低,能够高效处理大量的IO操作并保持高并发性。
- 多进程:也可以使用,但进程上下文切换开销较大,一般不如多线程高效。
计算密集型任务:
- 多进程:适合计算密集型任务,因为各进程独立运行,不会相互干扰,可以充分利用多核CPU资源。
- 多线程:也可以使用,但需要注意线程间的同步和资源竞争问题。
理解协程不被操作系统内核管理,而完全由程序控制
- 用户态调度:协程是完全由用户程序在用户态进行调度和切换的,不依赖操作系统内核进行上下文切换。
- 轻量级:协程切换仅需保存和恢复少量上下文信息(如程序计数器和局部变量),开销极低。
- 协作式多任务:协程主动让出执行权(通过
yield
或类似操作),由程序显式控制切换时机和顺序。
用户级线程和内核级线程的区别
用户级线程(User-Level Threads):
- 实现:在用户态库中实现,操作系统内核不感知。
- 调度:由用户级线程库管理,用户态调度,开销较小。
- 上下文切换:仅在用户态完成,不涉及内核,速度快。
- 缺点:无法利用多处理器并行能力,一个线程阻塞会导致整个进程阻塞。
内核级线程(Kernel-Level Threads):
- 实现:由操作系统内核直接支持和管理。
- 调度:由内核调度程序管理,能够利用多处理器并行执行。
- 上下文切换:涉及内核态和用户态的切换,开销较大。
- 优点:可以实现真正的并行处理,一个线程阻塞不会影响其他线程。
总结
- 进程访问隔离:通过独立地址空间、PCB和安全机制实现。
- 线程数据:栈和寄存器是私有的,全局变量、静态变量、堆和文件描述符是共享的。
- 任务适配:IO密集型任务适合多线程,计算密集型任务适合多进程。
- 协程调度:由用户程序控制,不涉及操作系统内核。
- 线程级别:用户级线程在用户态实现和调度,内核级线程由内核管理和调度。
6、进程有哪些状态,怎么转换的?线程状态及转化?进程的创建需要系统分配什么资源?僵尸进程和孤儿进程是什么,具体怎么查看?守护进程是什么?进程可以忽视信号吗?
进程状态及其转换
进程状态
- 创建(New):进程正在被创建。
- 就绪(Ready):进程已准备好运行,等待被分配CPU。
- 运行(Running):进程正在CPU上执行。
- 等待/阻塞(Waiting/Blocked):进程等待某个事件(如I/O操作完成)。
- 终止(Terminated):进程已完成执行或被终止。
- 挂起(Suspended):进程暂时不活动,可能被从内存中移到磁盘上。
状态转换
- 创建 -> 就绪:进程创建完成,进入就绪队列。
- 就绪 -> 运行:调度器选择进程,分配CPU执行。
- 运行 -> 等待/阻塞:进程等待I/O或其他事件完成。
- 等待/阻塞 -> 就绪:等待事件完成,重新进入就绪队列。
- 运行 -> 就绪:时间片结束,进程回到就绪队列。
- 运行 -> 终止:进程执行完成或被强制终止。
- 就绪 -> 挂起:进程被挂起,移出内存。
- 挂起 -> 就绪:挂起结束,进程回到内存中。
线程状态及其转换
线程状态
- 新建(New):线程对象被创建。
- 就绪(Runnable):线程准备运行,等待CPU调度。
- 运行(Running):线程正在执行。
- 等待(Waiting):线程等待另一个线程通知或事件完成。
- 超时等待(Timed Waiting):线程等待指定时间后重新就绪。
- 阻塞(Blocked):线程等待某个锁。
- 终止(Terminated):线程完成执行或被终止。
状态转换
- 新建 -> 就绪:线程调用
start()
方法。 - 就绪 -> 运行:CPU调度线程执行。
- 运行 -> 等待:线程调用
wait()
或类似方法。 - 等待 -> 就绪:等待结束,重新调度。
- 运行 -> 超时等待:线程调用
sleep()
等方法。 - 超时等待 -> 就绪:等待时间结束,重新调度。
- 运行 -> 阻塞:线程等待锁释放。
- 阻塞 -> 就绪:锁释放,重新调度。
- 运行 -> 终止:线程执行完成或被强制终止。
进程创建需要系统分配的资源
- 进程控制块(PCB):存储进程状态、寄存器信息、调度信息等。
- 地址空间:为代码段、数据段、堆和栈分配内存。
- 文件描述符表:为进程打开的文件分配文件描述符。
- 内核资源:包括进程表项、调度队列等。
僵尸进程和孤儿进程
僵尸进程
- 定义:已终止但其父进程尚未调用
wait
或waitpid
获取其退出状态的进程。 - 查看:用
ps aux | grep Z
命令查看,状态为Z
表示僵尸进程。 - 处理:父进程调用
wait
或waitpid
回收子进程。
孤儿进程
- 定义:父进程先于子进程终止,子进程成为孤儿进程。
- 查看:孤儿进程由
init
(PID 1)进程接管,可以用ps -ef | grep <PPID>
查看父进程ID为1的进程。 - 处理:
init
进程会自动回收孤儿进程的资源。
守护进程
- 定义:在后台运行的进程,不与任何终端关联,通常在系统启动时启动,提供系统级服务。
- 特性:通常在系统启动时启动,并在后台运行,为其他程序或用户提供服务,如
cron
、sshd
等。
进程可以忽视信号吗?
- 信号处理:进程可以通过信号处理程序处理信号,也可以选择忽略某些信号。
- 忽略信号:通过调用
signal
或sigaction
函数,将信号处理程序设置为SIG_IGN
。 - 不可忽略的信号:有些信号不能被忽略,如
SIGKILL
和SIGSTOP
,这些信号用于强制终止或暂停进程。
总结
- 进程状态:创建、就绪、运行、等待/阻塞、终止、挂起,状态间根据事件进行转换。
- 线程状态:新建、就绪、运行、等待、超时等待、阻塞、终止,状态间根据事件进行转换。
- 进程资源:PCB、地址空间、文件描述符表、内核资源。
- 僵尸进程和孤儿进程:僵尸进程已终止但未回收,孤儿进程父进程已终止。
- 守护进程:后台运行的进程,提供系统服务。
- 信号处理:进程可忽略某些信号,但
SIGKILL
和SIGSTOP
等信号不可忽略。
7、协程是在生命周期的哪一步?什么情况适合用协程池,什么情况适合用线程池?fork会拷贝页表吗?线程的调度怎么完成?
协程是在生命周期的哪一步?
协程的生命周期
协程的生命周期和线程有些相似,但更加轻量化,通常包括以下几个状态:
- 创建(Created):协程对象被创建,但尚未开始执行。
- 就绪(Ready):协程已经准备好执行,等待调度器分配执行机会。
- 运行(Running):协程正在执行中。
- 挂起(Suspended):协程在执行过程中被主动挂起,等待被唤醒重新执行。
- 结束(Terminated):协程执行完毕或被终止。
协程的状态转换通常由用户代码控制,而非操作系统内核。具体的控制步骤如下:
- 从创建到就绪:协程对象初始化完成。
- 从就绪到运行:调度器分配执行机会,开始执行协程代码。
- 从运行到挂起:协程主动调用
yield
或类似操作,暂时让出执行权。 - 从挂起到就绪:外部事件或其他协程唤醒,协程重新进入就绪状态。
- 从运行到结束:协程执行完毕或遇到终止条件,进入终止状态。
适合用协程池和线程池的情况
协程池
- I/O密集型任务:协程适合处理大量I/O操作,如网络请求、文件读写等。在这些场景下,协程的轻量级上下文切换和非阻塞特性可以显著提高并发处理能力。
- 高并发轻量任务:适合大量小任务需要高并发处理,如Web服务器的请求处理、事件驱动的系统。
线程池
- CPU密集型任务:线程适合执行计算密集型任务,如复杂计算、数据处理等。线程可以充分利用多核CPU资源,实现真正的并行计算。
- 需要操作系统级别支持的任务:如多线程应用中需要利用操作系统提供的多核并行能力、并需要线程间同步机制(如锁、信号量等)的场景。
fork
会拷贝页表吗?
是的,fork
会拷贝页表,但具体的拷贝机制依赖于操作系统的实现,通常是采用写时复制(Copy-On-Write, COW)机制:
- 页表拷贝:
fork
系统调用创建子进程时,父进程的页表会被拷贝到子进程,但实际的物理内存页不会立即拷贝。 - 写时复制:父子进程共享相同的物理内存页,直到其中一个进程尝试写入某个页时,内核才会复制该页,以确保父子进程拥有独立的内存副本。
线程的调度怎么完成?
线程调度的实现
线程调度是操作系统的一个关键功能,它决定了哪些线程可以在何时运行。线程调度可以在用户态和内核态完成:
-
内核态调度:
- 内核级线程(Kernel-Level Threads, KLTs)由操作系统内核调度。
- 调度器根据调度算法(如时间片轮转、优先级调度等)选择下一个要运行的线程。
- 调度过程涉及保存和恢复线程上下文(如寄存器、程序计数器等)。
-
用户态调度:
- 用户级线程(User-Level Threads, ULTs)由用户级线程库调度。
- 用户态调度器在用户空间选择和切换线程,不涉及内核,因此切换开销较小。
- 需要注意用户态调度的限制:一个用户级线程阻塞可能导致整个进程阻塞。
调度算法
- 时间片轮转(Round Robin):每个线程按时间片轮流执行,时间片结束后切换到下一个线程。
- 优先级调度(Priority Scheduling):根据线程的优先级选择下一个执行的线程,高优先级线程优先执行。
- 多级反馈队列(Multilevel Feedback Queue):多级队列结合时间片轮转和优先级调度,线程可以在不同级别队列间移动,适应不同类型的负载。
- 短作业优先(Shortest Job Next, SJN):优先调度预计执行时间最短的线程,适用于某些特定场景。
总结
- 协程生命周期:协程的状态包括创建、就绪、运行、挂起和结束,状态转换由用户代码控制。
- 协程池和线程池的适用情况:协程池适合I/O密集型和高并发轻量任务,线程池适合CPU密集型任务和需要操作系统级别支持的任务。
fork
会拷贝页表:但采用写时复制机制,实际物理内存页只有在写操作时才被复制。- 线程调度的实现:包括内核态调度和用户态调度,采用不同的调度算法如时间片轮转、优先级调度等。