初见多线程并发编程

初见多线程并发编程

什么是进程?

我们每次打开一个软件其实都是打开了一个exe后缀的文件,这些文件叫做可执行文件,这些可执行文件被双击之前都是静静的躺在硬盘上,此时的他们对我们的系统没有任何影响,一旦进行了双击的操作,操作系统就会把这个.exe加载到内存中,并且执行.exe内部的一些指令(.exe文件内部就存储了很多这个程序对应的二进制指令)

上述运行起来的可执行程序,就称为”进程”.进程是操作系统进行资源分配的最基本单位

操作系统是如何管理进程的?

宏观上为2步:

  1. 描述进程
  2. 组织进程

  1. 描述进程(这里以单线程的进程为例)

    每个进程会用一个pcb块进行描述,这个pcb块就包含了很多的属性

    1. pid:线程自己的id ,每个线程的id都不一样,都有自己独一份的编

    2. 内存指针:执行这个进程要执行的代码/指令子内存的哪个区域,以及这个进程执行过程中所依赖的数据在内存的哪个位置

    3. 文件描述符表:可以给他抽象理解成一个数据,一旦一个进程要对文件进行操作,就会在文件描述符表中新增一项,用以描述被操作文件的相关信息,并且我们应当知道,只要进程启动了,就会默认打开三个文件:标准输入,标准输出,标准错误,其中文件描述符表的各个位置的下标就是文件描述符.

    4. 专用于进程调度的属性

为什么要进程调度/strong>

因为当代操作系统都是多任务操作系统,操作系统同时运行着多个进程,肯定是要考虑如何去调度这些进程的.

首先要知道并行和并发的区别:

  1. 并行:微观视角去看,是两个cpu核心同时跑两个任务的代码
  2. 并发:微观上看是一个cpu先执行一个任务的代码,再快速切换去执行第二个任务的代码,正是由于切换的足够的快,我们宏观上去看:这两个任务好像就是同时执行的一样

所以我们宏观上是无法区分并行并发的,所以同一用并发来进行概括

pcb专用于进程调度的属性??

  1. 状态:一个pcb可能有两个状态:就绪状态和阻塞状态,前者表明该pcb随时都可以放到cpu上去跑,后者则不可以
  2. 优先级:进程之间的被操作系统调度,也是分优先级的,有的进程就是会先调度,甚至调度的时间还比较长
  3. 记账信息:统计每个进程分别都在cpu上执行了多久了,分别都执行了哪些个指令,没被执行的线程都分别等了多久了,依据这些统计数据,操作系统将更加合理的去调度进程,或者换句话说,记账信息给进程调度提供了指导信息.
  4. 上下文:某个进程被调度出cpu的时候,由于后续可能还要继续执行这个进程,所以先将关于该进程的寄存器中的数据先存到内存中,后续再次调度到这个进程的时候,cpu又可以把之前存档的内存中的相关信息进行恢复(读档),存档+读档就是所谓的上下文属性.

2.组织进程

? 典型的实现就是用一个双向链表把一个个的pcb个串起来(Linux)系统中就是如此


内存资源是如何分配的?

以进程为基本单位进行分配,一个进程内的若干个线程共享该进程所分得的资源.

并且进程与进程之间通过虚拟地址空间的方式彼此分隔,这使得进程之间具备独立性,以致于操作系统不会由于一个进程挂了,而导致其他进程也挂.

但是进程之间在处理一些复杂业务的时候,也要借助多个进程进行交互才能完成,此时就有了一个能进行进程间”信息交换“的需求,主流操作系统中提供进程间通信机制又如下几种:

  1. 管道
  2. 共享内存
  3. 文件
  4. 信 量

什么是线程?

为什么要进行并发编程

  1. 单核cpu的算力达到了瓶颈,要想再提高算力,就需要多核cpu,而并发编程更能充分利用多核cpu资源
  2. 有些任务场景需要进行等待IO,为了让这个等待的时间能够去赶一下别的工作,也需要进行并发编程.

出现线程的原因:由于要进行并发编程,如果使用进程来开展,可行是可行,但是用进程来实现并发编程,会有很多问题,如:创建或者销毁一个进程就会涉及到资源的分配和释放,如果需要频繁进行进程调度,期间的资源申请和释放过程是一笔不小的开销.那如何解决这个多进程并发编程带来的问题,就有了如下的解决方案:

  1. 采用进程池:某进程不再执行,我们可以将该进程放进进程池中,后续又要使用该进程,直接无进程池中拿出来使用即可,这样就可以避免频繁的创建和销毁进程带来的资源开销.但是:闲置的进程放在那里也是要占用资源的,并且一旦进程池中的进程放的越来越多,这个闲置进程的资源开销也不小啊.所以采用进程池的方式来解决多进程并发编程的方法并不好
  2. 采用多线程:进程可以完成一个任务,我线程也可以,并且呢线程比进程来的更轻量,更轻量的原因有三:**创建和销毁一个线程相比较于创建和销毁一个进程要低很多;其次调度进程的成本也要比调度线程的成本高很多(线程是系统调度的最小单位).再之,一个进程中的若干线程共享同一份资源,所以除了第一次线程调度时,其所属进程需要进行资源分配,后续该进程中的线程再进行创建和销毁都不涉及资源的再申请和释放了,最后进程完全执行结束,最后再释放资源即可.**后来人们还不满足与多线程并发编程,然后又出现了线程池,和协程.
  3. 生活案例:一个工厂(一天能生产5000部手机)被下达一个任务,一天造出10000部手机,我可以有两条思路:1我再建一个工厂,两个工厂一起干;还有一个思路就是我在现有工厂里再加一条流水线,两个思路都能让任务达标,但后者显然付出的代价要更小.(进程是线程的容器)

?


多线程编程?

标准库中的线程类(Thread)??

操作系统提供了一组关于线程的API,JAVA基于这组API进行了进一步的封装,就成了Thread类

Thread的基本用法??

  1. 编写一个类继承Thread类,子类对父类的run()进行覆盖,调用start()后操作系统才会有这个线程,否则只有Thread对象

  2. 创建一个类,实现Runnable接口,实例化这个子类并将其作为形参传给Thread进行构造

  3. 借助匿名内部类的写法对1进行改写

  4. 借助匿名内部类对2进行改写

  5. 查看Runnable接口可与发现该接口是一个函数式接口(一个接口中只有一个抽象方法),所以我们可以在这里使用Lambda表达式


多线程并发案例??

  1. 并发打印

主线程上来就打开了t1线程,主线程和t1出现了抢占式执行,代码表示的是两个线程都是休眠1s然后再打印,但这个1s只能说明1s内不能被唤醒,没说1s后瞬间就会被唤醒,所以具体何时被唤醒取决于系统调度,最终出现了此处的随机打印结果.


2.使用多线程和不使用多线程时给两个数都自增10亿次,比较二者的时间差

public class Demo2 {    private static final int count=10_0000_0000;    public static void main(String[] args) {long start=System.currentTimeMillis();Thread t1=new Thread(()->{    long a=0;    for(long i=0;icount;i++){ a++;    }});t1.start();Thread t2=new Thread(()->{    long b=0;    for (long i=0;icount;i++){ b++;    }});

                                                        

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年3月6日
下一篇 2022年3月6日

相关推荐