并发与并行,线程与进程如何理解

一、前言

1、冯诺依曼体系结构
我们所认识的计算机,都是有一个个的硬件组件组成
输入单元:包括键盘,鼠标,扫描仪,写板等;中央处理器(CPU):含有运算器和控制器等;输出单元:显示器,打印机等
关于冯诺依曼,必须强调几点:
这里的存储器指的是内存,不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备),外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。一句话,所有设备都只能直接和内存打交道。
在这里插入图片描述
2、操作系统的定位
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:内核(进程管理,内存管理,文件管理,驱动管理);其他程序(例如函数库,shell程序等等)
在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件,类比一个银行体系来理解操作系统, 管理者的任务按照种类分两种:

硬型基础设备管理和软性业务管理
按照目标对象分两种:一种对内的管理,一种对外业务进行管理

在这里插入图片描述

二、进程

  • 程序:是一份给CPU指导的静态的一组有序的指令集合。
  • 进程:是遵照程序完成的一次过程(用户角度),进程是系统进⾏资源分配和调度的最小独⽴单位(OS角度)。每⼀个进程都有它⾃⼰的内存空间和系统资源
  • 线程:系统调度的一个最小单位
1、程序与进程

我们可以粗略的想象一下这个模型,计算机科学家如果要做西红柿炒鸡蛋,首先看菜谱上的步骤,1打鸡蛋,2切西红柿,3点火……,通过这个菜谱的步骤就可以得到一份西红柿炒鸡蛋;而我们的编写的代码,就相当于是菜谱,CUP相当于是科学家,通过程序这个菜谱,加工数据后得到了我们期望的数据再输出给外设。
而根据程序进行的一次执行过程,就是一个进程,所以很明显,一个程序可以有多个进程,进程是担当分配系统资源(CPU时间,内存)的实体,是具有动态特性的。一份程序,可以同时有多个进程在运行!程序必须以进程为表现,才能运行起来!

在这里插入图片描述
例如,张三去银行转账,就要使用银行的资源(接待,转账人员,银行电脑,必要时保安也可以跟着:) )为张三完成转账,而在银行内部,这一整套过程的执行,我们统称为办理业务,也是一个进程。
时间片
程序的执行过程

PC中保存的是要求CPU执行的下一条指令的地址
IR中保存的是根据PC读取的,下一条要执行的指令

  • 程序加载进内存中,由PC读取指令的地址
  • IR保存的是根据读取的地址或,得到的具体指令
  • 逻辑运算单元拿走IR指令,将指令加载到寄存器中执行
  • 控制单元不断读取指令,逻辑运算单元真正的干活

CUP的周期: ①根据PC,从内存读取下一条要执行的指令的地址,并放入IR中,②更新PC,③ALU运行IR中的指令

在这里插入图片描述
程序和进程区别:
①进程是程序及其数据在计算机的一次运行活动,它是动态地创建和消亡的,具有一定的生命周期。 而程序是一组有序的指令集合, 是一种静态概念 它是永久存在的,可长期保存。。没有建立进程的程序不能作为1个独立单位得到操作系统的认可。
②一个进程可以执行一个或几个程序, 一个程序也可以构成多个进程。进程可以创建进程,而程序不能形成新的程序。
③1个程序可以对应多个进程,但1个进程只能对应1个程序。进程和程序的关系犹如演出和剧本的关系。
④进程和程序的组成不同。从静态角度看,进程由程序、数据和进程控制块(PCB)三部分组成。而程序是一组有序的指令集合。

2、进程的调度与时间片

操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。
这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这是一种假同时

例子
计算机科学家正在做西红柿炒鸡蛋的时候,突然看到儿子摔倒了。多出来一个新的任务一扶儿子起来。计算机科学家:做西红柿炒鸡蛋/扶儿子起来
在这里插入图片描述
假设张三准备办理转账业务,但当他把所有资料给工作人员之后,工作人员告诉他,你现在办理不了,因为他现在需要填写一张申请表,此时,工作人员将他的资料保存起来,让他去一边填写资料,填写完毕之后,再回来继续办理,同时,张三去填表了,而工作人员继续给别人提供服务。这个过程叫做进程切换。张三表填完了,继续回到柜台,工作人员拿出他之前的资料,继续给张三办理业务,这叫做进程的上下文保护与恢复(为什么要这么做?因为进程的运行是在CPU上的,CPU有寄存器,保存的是进程运行的各种临时数据,为了达到切换和便于恢复的目的,就有了将CPU寄存器保存和恢复的做法,归根结底是为了接着上次的位置继续运行

后来,银行出台了规定,每个人在柜台办理任务的时间不能超过10分钟(以防止其他人长时间等待),所以为了更好的服务各个人员,银行工作人员将上面的切换与恢复的思路应用到各种业务中,所以长期来看,即便只有一个工作人员,也能同时服务多个客户,这种机制叫做基于时间片的进程轮转管理机制,而上面的10分钟,就是银行轮转的时间片,只要时间到了,客户下去等待,让其他用户来办理业务。而对每个人来说,在一段时间之内,可能所有人的业务都得以推进(即便没完成),而不至于大家长时间等待
进程的上下文:
上下文简单说来就是一个环境,进程在时间片轮转切换时,由于每个进程运行环境不同,就涉及到转换前后的上下文环境的切换就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容。切换时需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
进程的状态:
创建好进程之后,并不意味着,马上就获取到CPU 了。而只是就绪并且排队了。什么时候获取到CPU,得根据调度来看,站在进程的视角认为是随机的!等之后发生进程调度时,就可能被调度到CPU上运行了
在这里插入图片描述
就绪:进程处于可运行的状态,只是CPU时间片还没有轮转到该进程,则该进程处于就绪状态。
运行:进程处于可运行的状态,且CPU时间片轮转到该进程,该进程正在执行代码,则该进程处于运行状态。
阻塞:进程不具备运行条件,正在等待某个事件的完成。
用户态和内核态:
一般的操作系统(如Windows、Linux)对执行权限进行分级:用户态和内核态。
操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态。即运行OS写的逻辑
用户程序的权限最低,称为用户态。即运行开发者写的逻辑
如何理解?
就好比上面的例子,张三去填表,自己写姓名,电话,邮箱等等,做着自己的事情,这叫做用户态;而张三通过窗口的工作人员,把自己的需求给工作人员,自此,张三在等,银行工作人员在忙,对张三来讲,就叫做陷入内核。那么内核态是什么意思?就是工作人员在帮你办理业务时的状态

三、线程

1、有了进程为什么还要线程

进程已经是可以进⾏资源分配和调度了,为什么还要线程呢?
为使程序能并发执⾏,系统必须进⾏以下的⼀系列操作:
(1)创建进程,系统在创建⼀个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O设备,以及建⽴相应的PCB;
(2)撤消进程,系统在撤消进程时,⼜必须先对其所占有的资源执⾏回收操作,然后再撤消PCB;
(3)进程切换,对进程进⾏上下⽂切换时,需要保留当前进程的CPU环境,设置新选中进程的CPU环境,因⽽须花费不少的处理机时间。

进程调度,分派,切换时,都需要花费较⼤的时间和空间开销
引⼊线程主要是为了提⾼系统的执⾏效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理。使OS具有更好的并发性简单来说:进程实现多处理⾮常耗费CPU的资源,⽽我们引⼊线程是作为调度和分派的基本单位(取代进程的部分基本功能【调度】

还是回到我们之前的银行的例子中。之前我们主要描述的是个人业务,即一个人完全处理自己的业务。我们进一步设想如下场景:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread。
那这个和多进程又有什么区别的,最大的区别就是这些执行流之间是否有资源的共享。比如之前的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的。

JVM视角:
多个线程共享进程的堆和方法区资源但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

  • 程序计数器为什么是私有的?
    字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
  • 虚拟机栈和本地方法栈为什么是私有的?
    虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
    本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的
  • 堆和方法区
    堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
2、什么时候使用多线程
  • 想提升速度
  • 有些场景下会发生阻塞,但是又希望同时去做其它事情

单核单CPU场景下,也可能可以提升速度!,那么一定提示速度吗??多线程编程,不一定可以提升速度!因为当创建线程/销毁线程也是有成本的!
所以,如果事情比较小,创建线程的时间就已经可以把任务完成,创建线程就不值当了。

提升速度
单线程情况下:一个人存9块钱,等待排队的过程很久
多线程:叫了8个帮手排队,那么在等待队列中,每个帮手得到处理机的概率提高,最好情况是一路顺下去9个(假如的)。
提升的时间来自于获得一次处理机后不需要长期排队,轮到帮手的概率比一个人排会增大。

值得注意的是:多线程的存在,不是提⾼程序的执⾏速度。其实是为了提⾼应⽤程序的使⽤率,程序的执⾏其实都是在抢CPU的资源,CPU的执⾏权。多个进程是在抢这个资源,⽽其中的某⼀个进程如果执⾏路径⽐较多,就会有更⾼的⼏率抢到CPU的执⾏权

在这里插入图片描述
例子:


public class TimeOFThread {
    // 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
    private static final long count = 10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
// 使用并发方式
        concurrency();
// 使用串行方式
        serial();
    }
    /**  多线程的模式 */
    private static void concurrency() throws InterruptedException {
        long begin = System.nanoTime();
// 利用一个线程计算 a 的值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a--;
                }
            }
        });
        thread.start();
// 主线程内计算 b 的值
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
// 等待 thread 线程运行结束
        thread.join();
// 统计耗时
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("并发: %f 毫秒%n", ms);
    }

    /** 全部在主线程内计算 a、b 的值  */
    private static void serial() {
        long begin = System.nanoTime();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a--;
        }
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("串行: %f 毫秒%n", ms);
    }


}

防止阻塞:
一个人开饭店,只能干跑堂或者做饭,来了第二个人就正在做饭,而无法去接待,处于阻塞状态,此时叫一个人来跑堂,就相当于是创建了一个子线程。 例如斐波那契数列 主线程负责读入数据,子线程负责计算值。

/**
 * 计算斐波那契数列的值(计算时间较长)
 * 主线程负责通过 scanner 读取用户的输入
 * 专门启动其他线程,来负责计算
 */
public class BlockScene {
    // 时间复杂度是 O(2 ^ n)
    // 非常大的时间复杂度
    /** 计算斐波那契的方法*/
    private static long fib(int n) {
        if (n==1 || n==2) {
            return n;
        }
        return fib(n - 1) + fib(n - 2);
    }
/**  子线程只负责计算数值*/
    private static class FibThread extends Thread {
        private final int n;
        FibThread(int n) {
            this.n = n;
        }
        @Override
        public void run() {
            System.out.printf("fib(%d) = %d%n", n, fib(n));
        }
    }
/** 主线程 只负责录入*/
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        while (true) {
            // 主线程只负责接待客人
            int n = scanner.nextInt();
            //System.out.printf("fib(%d) = %d%n", n, fib(n)); 单线程的模式下
            new FibThread(n).start();   // 每次计算交给一个新人去处理 这里是匿名类
        }
    }
}

四、并发与并行

  • 并⾏:并⾏性是指同⼀时刻内发⽣两个或多个事件。 并⾏是在不同实体上的多个事件
  • 并发
    并发性是指同⼀时间间隔内发⽣两个或多个事件。并发是在同⼀实体上的多个事件
    由此可⻅:并⾏是针对进程的,并发是针对线程的。

举个例子张三、李四、王五、属于一家公司,都同时去银为公司办理不同的业务,比如贷款业务,存款业务,购买产品。此时银行规定去 每个人在柜台办理任务的时间不能超过10分钟(以防止其他人长时间等待),所以为了更好的服务各个人员,银行工作人员将上面的切换与恢复的思路应用到各种业务中,所以长期来看,即便只有一个工作人员,也能同时服务多个客户, 10分钟就是银行轮转的时间片,只要时间到了,客户下去等待,让其他用户来办理业务。而对每个人来说,在一段时间之内,可能所有人的业务都得以推进(即便没完成),而不至于大家长时间等待,这种机制就叫做并发

而假如另外一个公司来了五个人办业务,此时银行又新增了一个窗口为他们服务,这就叫并行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值