线程
上回说到
进程 = > 运行起来的程序 = > 并发编程 = > 多核 CPU
操作系统管理进程:
- 先描述 = > PCB 结构体 来把进程的各种属性都表示出来
- 再组织 = > 通过链表数据结构把多个 PCB 串起来.
PCB 核心属性(进程调度)
- pid 进程标识符
- 内存指针, 该进程依赖的 指令 和 数据 在哪里
- 文件描述符表, 该进程打开了哪些文件
- 状态, 优先级, 上下文, 记账信息
“一核不够, 多核来凑”, 当前的 CPU 都是 多核心 CPU
但是需要通过一些特定的编程技巧(并发编程----并行 + 并发), 把要完成的任务, 拆解成多个部分, 并且分别让他们在不同的 CPU 上运行.否则, 多核心 CPU 就形同虚设了----“一核有难, 多核围观”.
通过 “多进程” 编程的模式, 其实就可以起到 “并发编程” 的效果.因为 进程 可以被调度到不同的 CPU 上运行, 此时则可以解决问题.
虽然, 多进程编程 可以解决上述问题, 但也带来了新的麻烦.
一个服务器,要能够同时给多个客户端提供服务,如果同时间, 来了很多客户端, 服务器如果只能利用一个 CPU 核心工作, 速度就会很慢.
一种典型的做法:
每个客户端连上连上服务器, 服务器都创建一个进程, 给客户端提供服务, 这个客户端断开了, 服务器再把进程给释放掉. 若这个服务器频繁的有客户,则服务器也需要频繁创建/销毁进程. 过于频繁则会使服务器响应速度变慢.
线程(thread)
注意读音
线程, 本身是操作系统提供的, 操作系统提供了 API 让我们操作线程.
JVM 就对操作系统 API 进行了封装.
线程这里, 提供了 Thread 类, 表示线程
我们引入线程, 主要的初心, 就是为了解决 上述 “进程” 太重量问题(创建/销毁 开销过大).
线程, 可以理解为 “进程的一部分”, 一个进程中, 可以包含一个线程, 或者多个线程, 若干个 PCB 联合在一起, 是描述一个进程的.
PCB
- pid; (每个线程都不一样);
- 内存指针;
- 文件描述符表;
(同一个进程的若干个线程, 这里的 内存指针 和 文件描述符表, 其实是同一个); - 状态, 上下文, 优先级, 记账信息;(每一个线程有一组自己的属性);
- tgid (同一个进程的 tgid 是同一个).
同一个进程中的若干线程之间, 是共同系统的内存资源和文件资源的.
(线程1 中 new 个对象, 线程2 是可以访问到的; 线程1 打开一个文件, 线程2 也是可以之间使用的),但是每个线程都是独立在CPU上调度执行的.
进程是系统资源分配的基本单位
线程是系统调度执行的基本单位
为什么说, 线程比进程更轻量? 为什么说线程的传教/销毁的开销, 比进程更小?
核心在于, 创建进程, 可能要包含多个线程, 这个过程中, 涉及到 资源分配/资源释放.
而创建线程(创建进程----含有一个线程的也是进程), 同一个进程包含 N个线程, 这些线程之间是公用资源, 相当于 资源 已经有了, 省去了 资源分配/资源释放 的步骤了. (申请资源和释放资源是个很重量的事情).
举个栗子
-
如 桌有一蛋糕, 一个人吃, 是一个线程, 两人吃, 是引入了新线程.
-
随着引入的线程越多, 每个线程要完成的任务量就更小了, 蛋糕吃完的速度就会更快.
-
继续引入
引入线程过多, 发现桌子的大小是有限的, 导致有些人无法吃到蛋糕, 无法和桌旁的人 “并发” 吃蛋糕. 并且外围的人往里挤, 则可能把里面正在吃蛋糕的人打断. 此时, 线程的开销就会很明显, 程序的性能可能不升反降. -
多个线程共同完成某个任务
即使不满的情况下, 多个人之间, 也是可能发生冲突的.
1号人, 看上了一个草莓
2号人, 也看上了同一个草莓
则会导致冲突, 程序就会出现 BUG
这就是 [线程安全问题] -
若某个人出现了问题, 直接掀桌, 则使别的人也吃不成
一个程序抛出异常, 并且没有很好的捕获成功, 则会使整个进程退出, 其他线程也就崩了.
相比之下, 进程与进程之间, 独立性就比线程的独立性更好, 一个进程挂了, 一般不会影响其他进程.
完