本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
- 🚀 魔都架构师 | 全网30W技术追随者
- 🔧 大厂分布式系统/数据中台实战专家
- 🏆 主导交易系统百万级流量调优 & 车联网平台架构
- 🧠 AIGC应用开发先行者 | 区块链落地实践者
- 🌍 以技术驱动创新,我们的征途是改变世界!
- 👉 实战干货:编程严选网
1 线程的引入
引入理由:
- 应用需要
- 开销考虑
- 性能考虑
1.1 应用需要
一个web服务器工作方式:
- 从客户端接收网页请求
- 从磁盘上检索相关的网页,读入内存(此时进程是停止的,直到读取完毕)
- 将网页返回给对应客户端
可以看到每次从磁盘读取的时候进程都是暂停的,导致性能低下。咋提高服务器工作效率?通常使用网页缓存。在没有线程情况下的两种解决方案:
- 一个服务进程undefined,也是一种顺序编程,虽采用缓存机制,但性能同样不高。而若设置多个进程,这多进程间又相互独立,有独立的地址空间,所以不能共享信息
- 有限状态机undefined,这种方式编程模型复杂,采用非阻塞
I/O
多线程的解决方式
这是一个多线程的web服务器的工作方式,首先读取客户端的请求,之后由分派线程将各个任务分派给工作线程,这里还是采用了网页缓存。
一个web服务器的实现有三种(构造服务器的方法):
| 模型 | 特性 |
| 多线程 | 有并发、阻塞系统调用 |
| 单线程进程 | 无并发、阻塞系统调用 |
| 有限状态机 | 有并发、非阻塞系统调用、中断 |
6.1.2 开销的考虑
进程相关的操作:
- 创建进程
- 撤销进程
- 进程通信
- 进程切换
时间/空间开销大,限制了并发度的提高。
线程的开销小:
- 创建一个新线程花费时间少(撤销亦如此)
- 两个线程切换花费时间少
- 线程之间相互通信无须调用内核(同一进程内的线程共享内存和文件)
6.1.3 性能的考虑
如果有多个处理器的话,一个进程就会有多个线程同时在执行了,这样可以极大的提高运行性能
2 线程的基本概念
在同一进程增加了多个执行序列(线程)。
线程:进程中的一个运行实体,是CPU的调度单位,有时将线程称为轻量级进程
进程的两个基本属性:
- 资源的拥有者:进程还是资源的拥有者
- CPU调度单位:线程继承了这一属性
线程的属性
- 有标识符
ID - 有状态及状态转换
-->需要提供一些操作 - 不运行时需要保存的上下文(程序计数器等寄存器)
- 有自己的栈和栈指针
- 共享所在进程的地址空间和其他资源
- 创建、撤销另一个线程(程序开始是以一个单线程方式运行的)
3 线程机制的实现
三种实现机制:
- 用户级线程
- 核心级线程
- 混合(两者结合)方法
用户级线程
在用户空间建立线程库:提供一组管理线程的过程。
运行时系统:完成线程的管理工作(操作、线程表)。
内核管理的还是进程,不知道线程的存在。
线程切换不需要内核态特权。
例子:UNIX。
线程是由运行时系统管理的,在内核中只有进程表。典型例子是UNIXundefined POSIX线程库--PTHREAD
POSlX(Portable Operating System Interface)多线程编程接口,以线程库方式提供给用户
| Thread call | Description |
| Pthread_create | Create a new thread |
| Pthread_exit | Terminate the calling thread |
| Pthread_join | Wait for a specific thread to exit |
| Pthread_yield | Release the CPU to let another thread run |
| Pthread_attr_init | Create and initialize a thread's attribute structure |
| Pthread_attr_destroy | Remove a thread's attribute structure |
优点
- 线程切换快
- 调度算法是应用程序特定的
- 用户级线程可运行在任何操作系统上(只需要实现线程库)
缺点
- 内核只将处理器分配给进程,同一进程中的两个线程不能同时运行于两个处理器上
- 大多数系统调用是阻塞的,因此,由于内核阻塞进程,故进程中所有线程也被阻塞。(可以在调用之前判断进行解决,如果是阻塞线程,那么就换其他线程)
核心级线程
内核管理所有线程管理,并向应用程序提供API接口。
内核维护进程和线程的上下文。
线程的切换需要内核支持。
以线程为基础进行调度
例子:Windows
混合模型
- 线程创建在用户空间完成
- 线程调度等在核心态完成
- 例子如
Solaris操作系统
4 线程状态(Java)
新建
创建后尚未启动的线程。
运行
包括 OS 中 Running 和 Ready 状态,该态的线程可能正在运行,也可能正在等待 CPU 为其分配执行时间
无限期等待
该态线程不会被分配 CPU 执行时间,要等待其他线程显式唤醒。如下方法会让线程进入无限期等待:
- 未设置 timeout 的 object.wait()
- 未设置 timeout 参数的 Thread.join()
- LockSupport.park()
有限期的等待
该态的线程也不会被分配 CPU 执行时间,不过无需等待被其他线程显式唤醒,而是在一定时间后,由 OS 自动唤醒
- 设置了 timeout 的 object.wait() 方法
- 设置了 timeout 参数的 Thread.join()
- LockSupport.parkNanos()
- LockSupport.parkUnit()
阻塞 V.S 等待
- 阻塞态在等待获取一个排它锁,该事件将在另外一个线程放弃这个锁时发生
- 等待状态在等待一段时间或唤醒动作
结束
已终止线程的线程状态,线程已结束执行。
5 进程的通信类型
- 共享存储器、管道、客户机-服务器系统(socket)
- 直接通信、间接通信(信箱)
IPC,进程间通信(inter-process communication),允许进程之间互传数据和指令。多个进程同时运行时,可用IPC使它们协作完成任务,提高系统资源利用率和效率。
常见IPC机制包括管道、消息队列、共享内存和套接字等。
管道
允许一个进程将输出发送到管道,然后另一个进程可以从该管道读取输入。
Linux管道可用 "|" 符号表示,如:command1 | command2。即 command1 的输出自动成为 command2 的输入。
管道只是一种通信机制,使进程能以更高效简单的方式协作。由于管道功能强大且能与其他命令联合使用,是编写复杂 shell 脚本的基础。
管程(Monitors)
也称监视器,一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。
管程实现了在一个时间点,最多仅一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度简化程序设计。
管程提供了一种机制,线程可临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。