线程概述
[牢记:单个处理器仅可以运行一个单线程进程或者一个线程]{.red}
线程概念
线程引入:
问题:多个用户向网站的服务器发出请求,仅具有单个处理请求的程序的服务器效率显然是非常低的
改善方案 V1:
- 服务器在接收到多个用户的请求时 [创建多个进程]{.blue} 用于处理多个用户的需求
- 并利用 [上下文切换]{.blue} 交替执行多个进程
方案问题:
- 进程的 [创建]{.red} 非常消耗处理器的资源而且效率非常低
- 进程的 [上下文切换]{.red} 效率非常低
解释:
- 创建的多个程序都是完成相同的工作使用相同的资源,但是每个进程都具有自己独立的物理内存,这显然是非常浪费的
- 所以考虑是否可以将多个完成相同任务的 “进程” 合并在一起,多个 “进程” 共享相同的物理内存
线程定义:
- 线程是包含 [程序计数器、寄存器、堆栈]{.red}
- 并且拥有所属进程的 [代码段、数据段、硬件资源]{.red} 的处理器的 [基本单元]{.red}
线程特点:
[线程是处理器调度的最小单位,进程是资源分配的最小单位]{.red}
:::danger
核心:线程的引入将进程的资源分配和调度执行完全分开,各个线程可以共享进程提供的资源又可以独立调度
:::
[每个线程都具有自己 独立 的程序计数器、寄存器、堆栈]{.red}(与执行过程相关)
所有线程共享进程提供的代码段、数据段、硬件资源(与硬件资源相关)
进程拥有的所有线程的虚拟地址和物理地址都是完全相同的
所有线程自身拥有的所有资源是不受保护的:每个线程都可以访问其他线程的拥有的资源
:::info
注:线程自身拥有的资源主要是程序计数器、寄存器、堆栈
:::
线程优点:
- 改善响应性:与用户交互的线程堵塞 -> 可切换其他线程继续执行
- 节省资源:创建线程消耗的资源相对较少
- 共享资源:进程的资源共享都需要使用相应的机制;线程与生俱来就是资源共享的
状态:(1) 初始态 (2) 就绪态 (3) 阻塞态(++blocked++) (4) 运行态 (5) 终止态
线程池:
- 引入:
- 每次进程都会执行 [创建线程 -> 线程执行任务 -> 线程被销毁]{.blue} 这个流程
- 每次进程都会执行创建线程的操作,如果创建的线程数量过多,可能导致耗尽操作系统所有资源
- 定义:进程在被创建之后立刻创建 [固定数量]{.red} 的线程等待请求的到来
- 执行过程:
- 进程接收到用户请求后立刻 [唤醒]{.red} 线程池中的 [某个线程]{.red} 用于处理当前的用户请求
- 线程结束用户请求的处理之后 [不会被立刻销毁]{.red} 而是被重新放回线程池中等待下一次用户请求的处理
- 优点:
- [节省时间]{.red}:线程的创建只在进程创建的时候,接收并处理用户请求时并不会花费时间去创建线程
- [限制线程的数量]{.red}:对于不支持高并发的系统来说是有好处的
- 引入:
改善方案 V2:
- 服务器创建分派线程:分派线程用于接收请求并创建和指定工作线程处理请求
- 服务器接收多个用户请求就相应地创建多个工作线程
- 每个工作线程由于只需要程序计数器,寄存器,堆栈较小的资源,所以创建是非常快的
- 服务器中的处理器采用调度算法不断地调度每个线程进行执行
细节:
线程又被称为轻量级进程(Light Weight Process:LWP)
每个线程也是具有 ++标识符++ 的
[每个线程都是不可以直接向操作系统申请资源的,只能够通过进程申请资源]{.red}
+++danger 问题:为什么每个线程的程序计数器,堆栈,寄存器是独立的?
解释:[每个线程执行的任务是不一样的,如果共用上述资源,那么利用上下文切换恢复之前执行的线程时就不可能知道之前的线程执行到哪里了]{.red}
+++
线程实现
- 用户级线程(User Thread)
- 内核级线程(Kernal-Level Thread)
- 混合实现
用户级线程
定义:
进程使用用户空间中提供的线程库
进程中的线程和操作系统中的线程是 {1:N} 实现的
:::info
注:使用用户空间中的线程库意味着操作系统只会将多线程视为单线程,因为没有深入到内核中去,操作系统感知不到
:::
特点:
[每个进程都需要维护相应的线程表,用于追踪该进程中的所有线程]{.red}
[内核无法得知多线程的存在也就无法管理,进程需要管理线程的创建、析构、调度操作]{.red}
:::info
注:这就意味着用户级线程无论是在线程库的实现,还是在线程库的使用上都会比较复杂
:::
优点:
- 用户级线程实现的进程可移植性高,适用于任何不同的操作系统
- Java 早期就是采用的这种方案,称为绿色线程(Green Threads)
- Golang 等新兴语言也是采用的这种方案
- [用户级线程效率非常高]{.red}
- 每次执行线程相关的操作只需要依靠用户空间中的线程库,不需要借助系统调用
- 用户级线程具有非常好的扩展性
- 用户级线程允许每个进程定义自己的调度算法
- 用户级线程实现的进程可移植性高,适用于任何不同的操作系统
缺点:[用户级线程实现的进程使用阻塞式系统调用时会阻塞当前进程]{.green}
- 因为并没有在内核中实现多线程,内核仍然认为进程都是单线程的,相当于调度的基本单位是进程
内核级线程
定义:
进程 [直接使用]{.blue} 操作系统(内核)提供的线程库
进程中的线程和操作系统中的线程是 [1:1]{.red} 实现的
:::info
① 进程实际上直接使用的是操作系统提供的内核线程接口:LWP(Light Weight Process)
② 每个 LWP 都会对应一个具体的内核线程
:::
特点:
- [内核管理所有线程的创建、析构、调度等操作,每个进程无法干预线程的行为]{.red}
- 每个进程最多向操作系统给出建议,但是操作系统不一定会采纳
- [内核维护所线程组成的线程表,每个进程不需要单独维护线程表]{.red}
- [内核管理所有线程的创建、析构、调度等操作,每个进程无法干预线程的行为]{.red}
优点:[采用内核级线程实现不会造成进程阻塞]{.red}
- 因为每个进程拥有的每个线程都对应一个内核线程,即使一个线程堵塞,其余线程依旧可以执行
缺点:
- [内核级线程效率相对低下]{.green}
- 线程每次执行操作都需要使用 [系统调用]{.red},操作系统就会从用户态陷入内核态,非常浪费时间
- [系统支持的内核线程数量是有限的]{.green}
- 每个线程都会占用操作系统的资源,显然能够提供的数量是有限的
- [内核级线程效率相对低下]{.green}
混合实现
定义:
- [用户级线程和内核级线程进行结合]{.blue}
- 进程中的线程和操作系统中的线程是 [M:N]{.red} 实现的
细节:Solaris、HP-UX 等类 UNIX 操作系统采用这种实现方式
线程与进程
- 对比:
- [创建线程的开销非常小 <-> 创建进程的开销非常大]{.red}
- [线程是调度的最小单位 <-> 进程是资源分配的最小单位]{.red}
- [每个线程之间的访问没有限制 <-> 每个进程之间通常是不可以相互访问的]{.red}
- 每个线程不可以直接向操作系统申请资源 <-> 每个进程可以向操作系统直接申请资源
- 线程上下文切换的速度更快 <-> 进程上下文切换的速度更慢
- 联系:
- 每个进程可以容纳多个线程
- 进程和线程都是可以并发执行的
- [所有线程都共享其所属进程的所有资源 -> 所有线程的物理地址和虚拟地址都是相同的]{.red}
- [子进程通常只会继承创建它的那个线程,不会继承父进程的其他子进程]{.red}
:::primary
参考博客:进程、线程和上下文切换
:::
线程其他
并发 & 并行
并发:多个线程被 [快速切换]{.red} 执行 (两个或多个事件在同一时间间隔发生)
:::info
① 处理器规定每个线程执行的时间,线程执行完规定时间后即被切换;从宏观角度看似所有线程是同时执行的,从微观角度看却不是这样
② 并发也可以看做是 “伪” 并行
:::
并行:多个线程 [同时]{.red} 执行
:::info
[注:并行只可以在多处理器系统中实现;单核处理器不可能真正实现并行]{.red}
:::
线程库(线程包)
定义:用户创建和管理线程的 API
分类:
Pthread:可提供用户级或者内核级的线程库
Windows:仅提供内核级的线程库
Java Thread:Java 线程的实现依托于宿主操作系统
:::info
Java Thread 中的大多数方法都是本地方法(Native Method),不同的操作系统实现是不同的
:::
:::primary
参考博客
[Java 多线程技术](一)线程和进程以及并行和并发的概念
:::