并发简史
# 并发简史
# 早期计算机 🔥
早期的计算机没有操作系统,从头到尾只执行一个程序,该程序可以访问计算机所有资源。
所以在该环境下,严重浪费了稀有的计算机资源,并且很难编写和运行程序。
# 操作系统、进程、线程出现
# 功能
- 每次可以执行多个程序,不同程序运行在不同的进程中
- 操作系统为各个独立的进程分配资源:内存、文件句柄、安全证书等
- 进程间可以通过粗粒度的通信机制来交换数据:套接字、信号处理器、共享内存、信号量、文件等
# 目的 🔥
操作系统、进程、线程出现,是为了解决如下问题(基于如下原因):
- 资源利用率 在等待IO时执行其他程序可以提高资源利用率
- 公平性 不同用户和程序对于计算机的资源有着同等的使用权,一种方式是通过粗粒度时间分片使用户和程序共享计算机资源,而不是有一个程序从头执行到尾,在启动下一个程序
- 便利性 在计算多个任务时,不应该只编写一个程序实现,应该每个程序执行一个任务,并在必要时互相通信
# 线程 🔥
上述也是线程出现的因素
- 现代操作系统中,都是以线程为基本的调度单位,不是进程!
- 线程允许在同一个进程中同时存在多个程序控制流
- 线程会共享进程范围内的资源:内存句柄、文件句柄。但每个线程有各自的程序计数器(PC)、栈、局部变量等
- 同一个程序(不同程序当然也可以)的多个线程可以同时被调度到多个CPU上运行
# 线程优势 🔥
- GUI 采用一个事件分发线程来代替主事件循环,提高用户界面响应灵敏度。
- 服务器 通过提高时钟频率来提高性能越来越困难,转而在单个芯片上放多个处理器核。 多线程程序可以在多处理器上执行,提高了处理器资源利用率从而提升系统吞吐率 多线程程序在单处理器上执行,在等待IO时可以让另一个线程继续执行,从而提高吞吐率
- 简化JVM实现 GC通常在一个或多个专门的线程中运行
- 建模的简单性 如:Servlet程序就像编写单线程程序一样,无需关心多少请求在同一时刻要被处理,无需了解Socket是否阻塞
- 异步事件简单化处理 单线程服务器应用必须使用非阻塞IO,其复杂性远高于同步阻塞IO,容易出错。 早期操作系统可创建线程数量比较少(数百),所以操作系统提供了实现多路IO的方法,如Unix的select、poll等。要调用这些方法,Java需要实现非阻塞IO(NIO)。而现代操作系统可创建线程数量有极大提升,为每个客户端分配一个线程也行
# 线程风险 🔥
# 安全性问题 🔥
- 由于同一个进程中的所有线程都共享进程的内存地址空间,所以这些线程可以访问相同的变量,并在同一个堆上分配对象
- 此时需要比在进程中共享数据更细粒度的数据共享机制,即明确的同步机制协同对共享数据访问。
如:++的非原子操作、指令重排问题
# 活跃性问题 🔥
如:死锁、饥饿、活锁
# 性能问题 🔥
活跃性问题意味着某件事情应该最终会发生,但是不够好。与活跃性问题相关的就是性能问题。
在多线程程序中,线程调度器挂起活跃线程转而运行另一个线程时,就会频繁出现上下文切换,该操作会带来极大的开销:保存和恢复执行上下文,丢失局部性,且CPU将更多花在线程调度上!而不是线程执行上!
上次更新: 2021/07/04, 16:21:48