Linux随笔 进程线程/串行并发并行/sleep阻塞原理

  1. 进程
    进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行
  2. 线程
    线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

一个正在运行的软件(如360安全卫士)就是一个进程,一个进程可以同时运行多个任务(病毒查杀,垃圾清理,软件卸载,每个任务就是一个线程), 可以简单的认为进程是线程的集合。

线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行。

  1. 单核CPU中,顺序编程(串行)
    顺序编程:程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。
public class Main {
     
    // 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完饭才能喝酒
    public static void main(String[] args) throws Exception {
     
		// 先吃饭再喝酒
        eat();
        drink();
    }

    private static void eat() throws Exception {
     
        System.out.println("开始吃饭?...\t" + new Date());
        Thread.sleep(5000);
        System.out.println("结束吃饭?...\t" + new Date());
    }

    private static void drink() throws Exception {
     
        System.out.println("开始喝酒?️...\t" + new Date());
        Thread.sleep(5000);
        System.out.println("结束喝酒?...\t" + new Date());
    }
}

Linux随笔 进程线程/串行并发并行/sleep阻塞原理_第1张图片
总共耗时10s

  1. 单核CPU中,并发编程
    并发编程:多个任务可以同时做,常用与任务之间比较独立,互不影响。
    单核CPU不停切换线程以达到并行的目的(切换时间非常短,在一个时间段内,近似于线程并行)
public class Main {
     
    public static void main(String[] args) {
     
	    // 一边吃饭一边喝酒
        new EatThread().start();
        new DrinkThread().start();
    }
}

class EatThread extends Thread{
     
    @Override
    public void run() {
     
        System.out.println("开始吃饭?...\t" + new Date());
        try {
     
            Thread.sleep(5000);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        System.out.println("结束吃饭?...\t" + new Date());
    }
}

class DrinkThread extends Thread {
     
    @Override
    public void run() {
     
        System.out.println("开始喝酒?️...\t" + new Date());
        try {
     
            Thread.sleep(5000);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        System.out.println("结束喝酒?...\t" + new Date());
    }
}

Linux随笔 进程线程/串行并发并行/sleep阻塞原理_第2张图片
总共耗时5s

所以并发能大大提高系统运行速度。

  1. 并行
    多核情况下,多线程分别在不同CPU上运行,从而达到真正的同一时间点,有多个线程。(单核并发在同一个时间点,只能有一个线程)

  2. 上诉案例补充分析

其实原作者这里并没有说清楚为什么一下子节省了5s,因为按照前面所说的,单核CPU中,多线程是利用CPU的快速线程切换(上下文切换)而达到并发的效果,即(因为是单核,所以只有并发处理的顺序执行)
Linux随笔 进程线程/串行并发并行/sleep阻塞原理_第3张图片
照理说,总量不变,但是为什么单核系统并发就节约了时间?以及单核系统并发操作一定会节约时间??

答案是看情况,单核系统的多线程并发不一定节约时间。

简单说一下,就是在非阻塞的情况下。单核环境下,多线程程序运行速度不快反而慢的多,因为CPU调度多线程上下文切换等消耗影响很严重。

但是这些是非阻塞的情况下,阻塞的情况下就不是这样了。以下是线程从创建、运行到结束的五个状态:新建状态、就绪状态、运行状态、阻塞状态及死亡状态

Linux随笔 进程线程/串行并发并行/sleep阻塞原理_第4张图片
从图中看到,分别在执行sleep()和wait()(wait就是手动进行线程挂起)的时候,会造成阻塞情况。其中,sleep会在一定时间后自动改变为ready状态(就绪)后,重新运行;而wait()需要手动进行notify()或者notifyAll()后才会变为ready状态。sleep和wait后,线程都会进入阻塞状态。

简单了解了阻塞状态后,再返回之前的例子可以发现,这里使用的是sleep对线程进行阻塞。

于是乎问题又来了,在sleep的时候,又发生了什么?

sleep的原理,可以参考这篇文章

学过MCU的都知道一个叫做定时器的玩意,这玩意是和主程序并行的,一个MCU有多个定时器。而sleep就是基于定时器的函数
所以sleep()在OS中的实现的大概流程如下:

  • 挂起进程(或线程)并修改其运行状态
  • 用sleep()提供的参数来设置一个定时器。
  • 当时间结束,定时器会触发,内核收到中断后修改进程(或线程)的运行状态。例如线程会被标志为就绪而进入就绪队列等待调度

简单来说就是sleep会给定时器设定一个初始时间,定时器内的时间会递减,当时间为0的时候会给内核一个中断信号,告诉内核线程已经就绪,可以重新运行。

好了,有了定时器的概念,就很好理解这个例子下并行发生了什么了。

串行的情况下,吃饭在运行到sleep后,因为sleep,线程被挂起,并开始使用定时器计时。计时5s后,开始执行后面的语句以及喝酒。喝酒在运行到sleep后,因为sleep,线程被挂起,并开始使用定时器计时。计时5s后,开始执行后面的语句并且结束。因为sleep前后的语句运行速度非常快,基本可以忽略不计,所以总时间就是sleep的时间,也就是串行执行的10s。

并发的执行吃饭和喝酒这两个线程,但是吃饭在运行到sleep后,因为sleep,线程被挂起,并开始使用定时器计时;同样,喝酒也因为sleep,线程被挂起,并开始使用定时器计时。而计时器是并行执行的,所以,一起执行5s后,吃饭和喝酒都重新回到了就绪状态,重新开始执行后面的语句。因为sleep前后的语句运行速度非常快,基本可以忽略不计,所以总时间就是sleep的时间,也就是并行执行的5s。

综上所述,在阻塞的情况下,并行能够大大缩短运行时间。当然,这是在sleep阻塞的情况下,其他情况具体情况具体分析。

案例参考文章:https://blog.csdn.net/vbirdbest/article/details/81282163

你可能感兴趣的:(Linux随笔,多线程,并发编程)