ch4 线程 threads
概述
线程:CPU 使用的基本单元,由线程 ID、程序计数器、寄存器集合和栈组成。
a sequential execution stream within process进程内的顺序执行流。
一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
多线程:由许多不同的并发活动组成的单个程序。
多线程进程中,线程与属于同一进程的其他线程共享代码段、数据段和其他操作系统资源。
Heavyweight Process = Process with one thread 传统重量级(heavyweight)进程只有单个控制线程
stack和thread不共享
多线程的优点
- 对用户响应度高:即使部分阻塞或操作冗长,仍能继续执行,增加对用户的响应程度;
- 进程内资源共享:多个不同的活动线程能够共享所属进程的内存和资源。
- 经济:创建和切换线程比进程需要更少的内存和资源分配。
- 利用多处理器体系结构:充分使用多处理器体系结构,每个进程能并行运行在不同处理器上。(单线程进程只能运行在一个 CPU 上,不论有多少 CPU。)
两种提供线程支持的方法
User Threads用户线程
受内核支持,无须内核管理(用户层面的线程库threads library管理)。用户线程对程序员可见,不需要内核干预,创建和管理相比内核线程更快,故用户线程更加高效。
但是内核只识别控制的进程(线程),当该进程的线程被阻塞时,该进程其他线程也被阻塞了。
可以被用在不支持线程的操作系统中。
每一个进程都可以有自己的调度算法
Kernel Threads 内核线程
内核维护进程和线程的上下文信息Kernel maintains context information for the process and the threads
应用程序无需执行线程管理No thread management done by application
受内核的支持。
内核在内核空间里进行线程的creation, termination, joining, and scheduling
在多处理器环境中,内核可以调度不同处理器中的线程。
优点:
- 在多处理器中,内核可以同时调度一个进程的多个线程
- 一个进程的某个线程被阻塞时,可以调用该进程其他线程
缺点:
- 线程间切换需要用到内核,时间增加
实现:
线程表追踪系统中所有线程。
所有可能阻塞线程的调用都作为系统调用实现。
Multithreading Models 多线程模型
多对一
多个用户线程映射到一个内核线程。用于不支持内核线程的系统。
任一时刻只有一个线程能访问内核,多个线程不能并行运行在多处理器上。
优点:由线程库在用户空间进行线程管理,效率较高;
缺点:如果一个线程执行阻塞系统调用,整个进程阻塞。
一对一
每个用户线程各映射到一个相应的内核线程。
允许多个线程并行运行在多处理器系统上。【并发性较高】
优点:一个线程执行阻塞系统调用,另一个线程能够继续执行。
缺点:往往需要限制系统所支持的线程数量。因为创建一个用户进程就需要创建一个相应的内核线程,该开销会影响性能。
多对多
n个用户线程映射到不多于n个的内核线程
虽然多对一模型允许开发人员创建任意多的用户线程,但是由于内核只能一次调度一个线程,所以并未增加并发性。虽然一对一模型提供了更大的并发性,但是开发人员应小心,不要在应用程序内创建太多线程(有时系统可能会限制创建线程的数量)。
多对多模型没有这两个缺点:开发人员可以创建任意多的用户线程,并且相应内核线程能在多处理器系统上并发执行。而且,当一个线程执行阻塞系统调用时,内核可以调度另一个线程来执行。
二级模型
多对多模型的变种,允许将一个用户线程绑定到某个内核线程上。
多线程问题 Threading Issues
系统调用fork()、exec()
fork()是只复制调用线程还是复制所有线程?
当调用fork()后立即调用exec()时,只复制调用线程(因为没有必要复制所有,exec()参数指定的程序会替换整个线程),否则就复制所有线程。
线程取消 Thread Cancellation
在线程完成之前终止线程的任务 。
异步取消 Asynchronous cancellation
立即终止目标线程。
在已经给目标线程分配资源或目标线程正在更新与其他线程共享的数据的情况下,操作系统从回收系统资源时不能将所有资源全部回收。
延迟取消 Deferred cancellation
目标程序定期检查是否应终止。
对于延迟取消,只有在目标线程检查了 flag,确定其是否应该取消后才会执行取消。Pthread 中,延迟取消时目标线程检查其是否应该取消的安全点为取消点(cancellation point)。
信号处理 Signal Handling
信号在 UNIX 中用来通知进程某个特定事件的发生。
信号有同样的模式:
- 特定事件产生
- 发送到进程
- 一旦发送,必须处理
信号可分为同步和异步:
- 同步信号:被发送到产生信号的同一进程。E.g. 非法访问内存、被 0 所除。
- 异步信号:由运行进程之外的事件产生,通常被发送到另一进程。E.g. 使用特殊键、定时器到期。
信号处理程序有两种:
默认信号处理程序(default signal handler):
- 每个信号都有一个,在内核中运行。
- 用户定义的信号处理程序:可对默认动作进行改写。
对于多线程程序,信号可能被发送到(依赖于信号类型):
- 信号所应用的线程;
- 进程内的每个线程;
- 进程内的某些固定线程;
- 规定一个特定线程以接收进程的所有信号。
线程池 Thread Pools
在进程开始时创建一定数量的线程,放入池中等待工作。服务器收到请求时,如有可用线程则唤醒池中的一个线程,并传递要处理的请求。线程完成服务后返回池中再等待工作。如果池中没有可用线程,则服务器一直等待直到有可用线程为止。
优点
- 用现有线程处理请求比等待创建新线程快;
- 限制在任何时候可用线程的数量,对不能支持大量并发线程的系统非常重要。
线程特定数据 Thread Specific Data
虽然同属一个进程的线程共享数据,有些情况下每个线程可能需要自己拥有的关于某些数据的副本。
调度程序激活 Scheduler Activations
多对多模型和二级模型都需要通信以确保分配给应用程序适量的内核线程。
轻量级进程(Lightweight process, LWP):实现多对多模型或二级模型的系统在用户线程和内核线程之间通常设置一种中间数据结构。
对于用户线程库,LWP 表现为一种应用程序可以调度用户线程来运行的虚拟处理器。每个 LWP 与内核线程相连,该内核线程被操作系统调度到物理处理器上运行。如果物理处理器上运行的内核线程阻塞,则与其相连的 LWP 也阻塞,关系链顶端与 LWP 相连的用户线程也阻塞。
upcall:内核通知应用程序与其有关的特定事件的过程。是调度程序激活提供的一种从内核到线程库的通信机制。由具有 upcall 处理句柄 的线程库来处理。
工作方式:
- 内核提供一组 LWP给应用程序,应用程序可调度用户线程到一个可用的LWP上。当一个应用线程将要阻塞时,事件引发内核发送 upcall 到应用程序,通知应用程序线程阻塞并标识特殊线程
- 内核分配一个新的 LWP 给应用程序
- 应用程序在该新 LWP 上运行 upcall handler
- 保存该阻塞线程的状态
- 放弃阻塞线程运行的原虚拟处理器
- upcall handler 调度另一个适合在新 LWP 上运行的线程
- 当原先阻塞的线程准备好执行时,内核发送另一个 upcall 到线程库,通知线程库原先阻塞的线程已经能够运行了(内核可能分配一个新的 LWP,或抢占一个用户线程并在其 LWP 运行用于处理该事件的 upcall handler)
- 应用程序将已处于未堵塞状态的线程标记为“能够运行”,调度一条合适的线程到可用 LWP 上运行。
操作系统实例
windows xp 线程
每个线程包含:
- a thread id
- register set
- separate user and kernel stacks
- private data storage area
linux 线程
不区分进程与线程,以任务称呼。
通过 clone() 被调用时传递的一组标志决定父任务与子任务发生多少共享。
本文来自博客园,作者:流云轻响,转载请注明原文链接:https://www.cnblogs.com/wozra/p/16247312.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律