程序执行副本

一个程序执行一定存在需要计算资源、存储资源,那么多个程序一起运行时,一定存在如何分配计算机有限的资源,那么这个过程中,每个程序就是一个进程。当有些进程运行中时,会进行图形渲染、请求网络、响应用户操作,需要在进程中并行这些任务(不阻塞),为了不给计算机增加负担,会用更轻量化的进程去做(即线程)。

设计思想

接下来我们思考几个核心的设计约束: image.png

  1. 进程和线程在内存中如何表示?需要哪些字段?
    • 描述信息:这部分是描述进程的唯一识别号,也就是 PID,包括进程的名称、所属的用户等。
    • 资源信息:这部分用于记录进程拥有的资源,比如进程和虚拟内存如何映射、拥有哪些文件、在使用哪些I/O 设备等,当然 I/O 设备也是文件。
    • 内存布局:操作系统也约定了进程如何使用内存。描述了一个进程大致内存分成几个区域,以及每个区域用来做什么。 每个区域我们叫作一个段。
  2. 进程代表的是一个个应用,需要彼此隔离,这个隔离方案如何设计?
    • 地址空间隔离(即物理隔离)
    • 内存空间隔离
  3. 操作系统调度线程,线程间不断切换,这种情况如何实现?
    • 注意中断帮助寄存器保存之前进程 / 线程的状态
    • image.png
    • image.png
  4. 需要支持多 CPU 核心的环境,针对这种情况如何设计?
    • 多个核心可以同时并行执行几个进程 / 线程
    • 因为寄存器保存了之前进程 / 现成的状态,所以可以实现多个核心切换

      分时执行

      一个进程(线程)运行的过程,会经历以下 3 个状态:
  • 进程(线程)创建后,就开始排队,此时它会处在“就绪”(Ready)状态
  • 当轮到该进程(线程)执行时,会变成“运行”(Running)状态
  • 当一个进程(线程)在等待其他操作时,会变成“阻塞”(Block)状态

image.pngimage.png

调度执行

  • 非抢占:先到先服务,保证公平性和吞吐量
  • 抢占:考虑优先级、最短任务优先(做分级队列)

    举例:Linux的O(n),O(1),CFS调度器,使用的策略有,静态动态优先级,时间片分配,全局/每个CPU/每个优先级分配一个队列,CPU任务窃取,虚拟时间,链表,红黑树

image.png

进程间通信

  • 管道
  • 共享内存
  • 消息队列
  • 远程调用

    锁 & 信号量

    竞争

    指多个线程对一个资源(内存地址)的读写产生竞争,在这个情况下,输出的值不可预测,取决于竞争时(临界区)的操作顺序,解决办法就是避免同时进入临界区(互斥

    抢占临界区,通过wait - notify实现竞争时的操作顺序,如果一个线程拿到锁,就继续执行,如果竞争失败,就调用wait方法将线程假如等待队列,当线程执行完毕,调用notify通知等待队列中的线程。

    信号量

    个互斥
    信号量个互斥

    饥饿 & 死锁

    线程1获得锁1,等待锁2;线程2获得锁2,等待锁1;于是两个线程都陷入了等待(饥饿),且等待永远不会结束,所以造成死锁需要满足以下4个基本条件

  • 资源抢占存在互斥逻辑

  • 持有等待
  • 禁止抢占(如果拿不到资源一直会处于等待状态,而不会释放已经拥有的资源)
  • 循环等待

解决办法:等待过程设计并发控制算法或超时 / 失败后协商转让

分布式锁

考虑多个服务容器,当多个容器同时调用修改用户积分服务时,一定会发生竞争,以操作Redis为例:

  1. 从Redis读用户积分
  2. 修改积分
  3. 更新Redis

    以上都属于原子操作(操作不可再分),由CPU指令提供,如cas、tas指令

这里就一定要加锁,有兴趣的可以了解一下Redis中setnx指令、Zookeeper节点操作等等

悲观锁 / 乐观锁

悲观锁:互斥排异,只保留最后一次更新(如MySQL的表锁、行锁、Java中的锁)
乐观锁:允许不同,先更新的先采纳,后更新的解决冲突(Git版本控制)

去中心化:区块链

不走中心化平台系统处理,不产生竞争,点对点交易,解决信用问题