Java并发编程之进程

来源:互联网 发布:java程序设计是什么 编辑:程序博客网 时间:2024/06/03 06:58

引入

操作系统中最核心的概念是进程:这是对正在运行程序的一个抽象。一个进程就是一个正在执行程序的实例、包括程序计数器、寄存器和变量的当前值。在多道程序设计中,一个 CPU 能在多个进程之间来回快速切换,达到(伪)并行效果。一个进程是某种类型的一个活动,它有程序、输入、输出以及状态。

操作系统的关键抽象:

这里写图片描述

内存大小有限,操作系统会为每个进程分配独立的虚拟地址空间。虚拟地址空间会映射到物理地址空间。JAVA虚拟机和VMWARE并没有CPU,硬盘等,可以说是一个软的虚拟机。

比喻 : 厨师做蛋糕

  • 做蛋糕的食谱: 程序
  • 做蛋糕的原料:输入数据
  • 厨师: CPU
  • 厨师阅读食谱,用原料做蛋糕的一系列动作的总和: 进程

  • 厨师的儿子跑进了,说是被蜜蜂蛰了

    • 厨师记录下当前做到哪一步了(保存当前进程状态)
    • 拿出急救手册,按其中的指示进行处理(开始另外一个进程)

进程在虚拟地址存储器中的逻辑布局

这里写图片描述

每个进程都有一个虚拟的地址空间, 这个地址空间是分块的。

这里写图片描述

程序运行中使用了多个寄存器,但是CPU只有一套寄存器。进程切换,寄存器的值需要保存在内存中,否则会被其他进程冲掉。

内存中的进程

这里写图片描述

struct task_struct{    pid_t pid; //进程号    long state;  //状态    cputime_t utime, stime;     // cpu在用户态和核心态下                                                        执行的时间    struct  files_struct *files;    //打开的文件    struct mm_struct *mm;    //进程使用的内存    ......}

进程的状态

这里写图片描述

运行–>>就绪: 进程在CPU中时间片用完
运行–>>等待:IO操作等。 等待就是说放入CPU维护的一个队列中去,根本没有占用cpu,真正占用CPU的是运行中的进程。

进程调度:到底谁应该占据CPU

  • 非抢占式
    • 调度程序一旦把 CPU分配给某一进程后便让它一直运行下去, 直到进程完成或发生某事件而不能运行时,才将CPU分给其它进程。
    • 适用于批处理系统
    • 简单、系统开销小。
  • 抢占式
    • 当一个进程正在执行时,系统可以基于某种策略剥夺CPU给其它进程。剥夺的原则有: 优先权原则、短进程优先原则、时间片原则
    • 适用于交互式系统

进程调度:评价标准

  • 公平

    • 合理的分配CPU
  • 响应时间短

    • 响应时间: 从用户输入到产生反应的时间
  • 吐量大

    • 吞吐量: 单位时间完成的任务数量
  • 但是, 这些目标是矛盾的!

批处理系统中的调度

  • 先来先服务

    • 公平、简单(FIFO队列)、非抢占、不适合交互式
  • 最短作业优先

    • 系统的平均等待时间最短
    • 但是需要预先知道每个任务的运行时间

交互式调度策略(1) :轮转

  • 每个进程分配一个固定的时间片

  • 假设进程切换一次的开销为1ms

  • 时间片为4ms

    • 20%的时间浪费在切换上
  • 时间片为100ms
    • 浪费只有1%, 但是假设有50个进程, 最后一个需要等待5秒 !

交互式系统调度策略(2):优先级

这里写图片描述
这里写图片描述

缺点:低优先级的进程可能会被饿死(永远得不到执行)。

调度策略(3):多级队列反馈

这里写图片描述

每个级别都有若干进程,每次执行都选择高优先级的进程执行,高优先级执行完了在执行低优先级。每一个进程执行完放到同一队列的尾部,同一级别采用时间片轮转策略。优先级高分配的时间短。还是会出现低优先级饿死,任务的优先级随着等待的时间增长优先级不断增高,动态调整。

进程间同步

这里写图片描述

生产者进程向队列中加入文件,消费者进程从队列中取走文件

生产消费模型代码实现:
这里写图片描述

  • 多进程情况下, 共享变量counter会出错!

这里写图片描述

  • 上图中 regist 类比成 寄存器
  • 对一个变量的操作,编译成机器码会是多行代码。所以我们在JAVA中写的count++不是一个原子操作。乱序执行的情况下就会出问题。

反思一下

这里写图片描述

  • 问题的核心

    • 不可控制的调度
    • 在机器层面, counter++, counter– 并不是原子操作
  • 临界区

    • 访问/修改共享资源(变量, 表,文件。。。)
    • 当进程进入临界区时,不允许其他进程在临界
      区执行

解决临界区问题

解决临界区问题(1):暴力手段,关闭中断

  • CPU 收到时钟中断以后,会检查当前进程的时间片是否用完, 用完则切换。

  • 在进程P进入临界区之前,通知OS, 不要做进程切换不就行了!

    • 关闭中断 (时钟中断), 这样CPU就不会被打断
    • 离开临界区,一定要记住打开中断
    • 但是,把中断操作开放给应用程序是非常危险的。会让宕机。

解决临界区问题(2):用硬件指令来实现锁

这里写图片描述
- 上图三条代码是原子的
这里写图片描述

为了确保原子性,系统会锁住总线,禁止其他CPU在函数执行完之前去访问内存

解决临界区问题(3):信号量

信号量S是个整数变量,除了初始化外,有两个操作,wait(), signal()
或者是P/V , 或者 down/up。

伪代码:
这里写图片描述
为了确保信号量能工作,需要用一种“原子”的方式实现它

解决临界区问题(5):不能忙等

这里写图片描述

list维护了一个等待进程列表。不像上一种方式在锁住的情况下,进程虽然获得了时间片但是什么事也干不了。通过维护一个等待进程列表解决了忙等问题。

解决临界区问题(6):用信号量解决打印问题

这里写图片描述

思考

  • 两个进程之间是怎么共享变量的?
    两个进程之间通过操作系统内核共享数据
原创粉丝点击