java基础巩固-宇宙第一AiYWM:为了维持生计,多高(多线程与高并发)_Part2~整起(线程与进程篇:进程状态、线程&进程&协程)

上回打手(线程)由于第一关卡中的三个里屋都被其他打手占用,所以在那等呀等~等呀等,便睡了过去。多高之线程篇,点这里去学一下线程呗
小胡和敏小言:停,你别先给我嘚吧嘚嘚吧嘚唧唧歪歪,你先给我把进程的官方定义和你的私方解释拿出来,我看一下干活

  • 进程=映射表上的资源+指令执行序列(线程);
    • 我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个 运行中的程序或者说程序的一次执行过程【系统运行一个程序从开始到结束对应进程从创建到运行到消亡的过程】,就被称为进程【进程是动态的】(Process)。
    • 进程搞来了一个单位的资源来分配调度,分配调度好了之后,线程兜里揣着这些资源去执行任务
      • 执行任务也就是进程要从硬盘读取数据时,硬盘处理速度堪称龟速,CPU 不需要阻塞等待数据的返回,而是去执行另外的进程。当硬盘数据返回时,CPU 会收到个中断,于是 CPU 再继续运行这个进程。
      • 咱们 在Java 中启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个JVM进程中的一个线程,也称主线程。
    • 一个进程中可以有多个线程,多个线程共享进程的堆和方法区(JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。其实这不就是咱们说的打手的私有空间和打手们的共享空间嘛。【线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。】
      • 进程的堆栈大小?
        • 32位Windows,一个进程栈的默认大小是1M,在vs的编译属性可以修改程序运行时进程的栈大小。
        • Linux下进程栈的默认大小是10M,可以通过 ulimit -s查看并修改默认栈大小
        • 默认一个线程要预留1M左右的栈大小,所以进程中有N个线程时,Windows下大概有N*M的栈大小
        • 堆的大小理论上大概等于进程虚拟空间大小-内核虚拟内存大小。windows下,进程的高位2G留给内核,低位2G留给用户,所以进程堆的大小小于2G。Linux下,进程的高位1G留给内核,低位3G留给用户,所以进程堆大小小于3G。
      • 一个进程最多可以创建多少个线程?跟两个因素有关:
        在这里插入图片描述
        • 进程的虚拟内存空间上限,因为创建一个线程,操作系统需要为其分配一个栈空间,如果线程数量越多,所需的栈空间就要越大,那么虚拟内存就会占用的越多。可以执行 ulimit -a 这条命令,查看进程创建线程时默认分配的栈空间大小【如果对于32位来说,假设创建一个线程需要占用 10M 虚拟内存【线程的栈确实有固定大小为10M字节】,总共有 3G 虚拟内存可以使用。于是我们可以算出,最多可以创建差不多 300 个(3G/10M)左右的线程。】
          在这里插入图片描述
          • 按照上面那种方式计算,64 位系统意味着用户空间的虚拟内存最大值是 128T,这个数值是很大的,如果按创建一个线程需占用 10M 栈空间的情况来算,那么理论上可以创建 128T/10M 个线程,也就是 1000多万个线程。但是肯定创建不了那么多线程,除了虚拟内存的限制,还有系统的限制,比如下面这三个内核参数的大小,都会影响创建线程的上限:
        • 系统参数限制,虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数,但是 有系统级别的参数来控制整个系统的最大线程个数
          在这里插入图片描述

那就先上菜。
在这里插入图片描述
胡&敏:是个这呀,大概有点感觉了,上次你说那个打手闯第一关的过程我大概清楚,今天这个进程是不是也有什么调度呀啥的。
对头,肯定有,上菜。(在此处还得感谢很多公众号里面的前辈,不管怎样,里面很多知识的学习和这些前辈都是密不可分的,可能正在阅读的您的文章就是我学习过的资料,特在此感谢三联,感谢各位前辈)
在这里插入图片描述

void schedule(void) {
    int next = get_max_counter_from_runnable();
    refresh_all_thread_counter();
    switch_to(next);
}

在这里插入图片描述
老规矩,买一赠一,下面就是进程切换过程中,用来存货的柜台。

struct task_struct{
	long state;
	long counter;
	long priority;
	struct tss_struct tss;
}

在这里插入图片描述
胡&敏:还可以哈,那就说明进程切换时用柜台存人家原来还没被执行完的任务的东西,等新的任务执行完在去柜台取回来继续执行。
胡&敏:那是不是切换后人家进程自己衣服口袋里装的是正在执行的应用程序的信息,把之前的被打断的任务存在了柜台,等把现在这个口袋里面的用完之后再切换回去时就会去柜台取原来的东西。
yes,伊特伊兹。
在这里插入图片描述

胡&敏:那我想,线程和进程应该都存在切换这个动作呀,原来任务的被打断,咱们肯定不能凭空把人家原来的任务的一些信息给白白扔了,得找个地方呀、柜台呀给存起来,让新任务进来进行执行,执行完了在找回原来任务的信息接着执行。大概意思明白了。
胡&敏:哦,那是不是和“中断”有关呀,我记得之前学过…
stop停,你话太多了,别说了,咱们还是回到进程吧。
上,常考点,进程的五个状态:创建、就绪、运行(执行)、终止、阻塞。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 新建态:创建进程时只含有PCB资源【创建状态(new) :进程正在被创建,尚未到就绪状态。】
    • 一个新进程被创建时的第一个状态就是新建态;
      • 操作系统允许一个进程创建另一个进程,而且允许子进程继承父进程所拥有的资源,当子进程被终止时,其在父进程处继承的资源应当还给父进程。同时,终止父进程时同时也会终止其所有的子进程【Linux 操作系统对于终止有子进程的父进程,会把子进程交给 1 号进程接管】。
      • 创建进程的过程 如下:
        • 申请一个空白的 PCB,并向 PCB 中填写一些控制和管理进程的信息,比如进程的唯一标识等
        • 为该进程分配运行时所必需的资源,比如内存资源
        • 将 PCB 插入到就绪队列,等待被调度运行
    • 被生出来,谁生出来,咋生出来,大家可以康康深入理解计算机系统,拜拜。
      • 分配PCB(一个用来记录进程信息,比如基址等的数据结构,操作系统组织进程全靠PCB)
      • 插入就绪队列
    • PCB:进程控制块(process control block):操作系统用来管理进程运行的数据结构,每个进程都在操作系统中有一个对应的PCB,PCB 是进程存在的唯一标识,这意味着一个进程的存在或者创建,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失
      • PCB 具体包含以下信息:
        • 进程描述信息:
          • 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符
          • 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务;
        • 进程控制和管理信息:
          • 进程当前状态,如 new、ready、running、waiting 或 blocked 等;
          • 进程优先级:进程抢占 CPU 时的优先级
        • 资源分配清单
          • 有关内存地址空间或虚拟地址空间的信息,所打开文件的列表和所使用的 I/O 设备信息。
        • CPU 相关信息
          • CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程重新执行时,能从断点处继续执行。
      • PCB运行过程:有链表和索引两种方式
        • 索引方式,它的工作原理:将同一状态的进程组织在一个索引表中,索引表项指向相应的 PCB,不同状态对应不同的索引表。
        • PCB通常是通过 链表 的方式进行组织【一般会选择链表,因为可能面临进程创建,销毁等调度导致进程状态发生变化,所以链表能够更加灵活的插入和删除。】,把具有相同状态的进程链在一起,组成各种队列
          在这里插入图片描述
          • 将所有处于就绪状态的进程链在一起,称为就绪队列
            在这里插入图片描述
          • 把所有因等待某事件而处于等待状态的进程链在一起就组成各种阻塞队列
            在这里插入图片描述
          • 对于运行队列在单核 CPU 系统中则只有一个运行指针了,因为单核 CPU 在某个时间,只能运行一个程序。
  • 就绪态【就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。】
    • 得到 除了CPU资源的其他OS资源,一旦得到CPU即可运行。 比如
      在这里插入图片描述
  • 运行态【运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。】
    • 得到了CPU资源,进程正在CPU上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态
  • 阻塞态【阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。】
    • 碰见顽疾了,比如要进行IO操作啥的… 。进程正在等待某一事件而暂停运行,比如等待某资源为可用或等待I/O完成。即使CPU空闲,该进程也不能运行。 一旦被阻塞等待,它只能由另一个进程唤醒,处于阻塞状态的进程是绝对不可能叫醒自己的。
    • 阻塞进程的过程 如下:
      • 找到将要被阻塞进程标识号对应的 PCB
      • 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行
      • 将该 PCB 插入到阻塞队列中去
    • 唤醒进程的过程 如下:
      • 在该事件的阻塞队列中找到相应进程的 PCB
      • 将其从阻塞队列中移出,并置其状态为就绪状态
      • 把该 PCB 插入到就绪队列中,等待调度程序调度
    • 如果有大量处于阻塞状态的进程,进程可能会占用着物理内存空间,而物理内存空间是有限的,被阻塞状态的进程占用着物理内存就一种浪费物理内存的行为。所以,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。那么,就需要一个新的状态来描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态。这跟阻塞状态是不一样,阻塞状态是等待某个事件的返回。
      • 导致进程挂起的原因:
        • 进程所使用的内存空间不在物理内存
        • 通过 sleep 让进程间歇性挂起,其工作原理是设置一个定时器,到期后唤醒进程
        • 用户希望挂起一个程序的执行,比如在 Linux 中用 Ctrl+Z 挂起进程
      • 挂起状态可以分为两种:
        • 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
        • 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立刻运行;
  • 终止态【结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。】
    • 进程可以有 3 种终止方式:正常结束、异常结束以及外界干预(信号 kill 掉)
    • 终止进程的过程 如下:
      • 查找需要终止的进程的 PCB
      • 如果处于执行状态,则立即终止该进程的执行,然后将 CPU 资源分配给其他进程
      • 如果其还有子进程,则应将其所有子进程终止
      • 将该进程所拥有的全部资源都归还给父进程或操作系统
      • 将其从 PCB 所在队列中删除

胡&敏:你这动不动就把锅甩给人家其他书了,你这也不行呀。

番外篇:

  • 🕴再捋一捋线程和进程的区别和联系
    在这里插入图片描述

    • 线程又称为轻量级进程(因为线程是进程内部的一部分,并且线程具有许多传统进程所具有的特征)或者进程元,通常一个进程都有若干个线程,至少包含一个线程
      • 当进程只有一个线程时,可以认为进程就等于线程【传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。】
      • 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时是不需要修改的
    • 进程是操作系统资源分配与管理的基本单位;线程是处理器CPU 任务调度和执行的基本单位
    • 每个进程都有 独立的代码和数据空间(程序上下文)、PCB(进程控制块,用来记录进程信息比如基址等的数据结构。主存中的进程就是一段连续的存在空间,里面包含了四部分:进程标识符、处理机状态、进程调度信息、进程控制信息,OS组织进程全靠PCB) ,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程 共享 进程的资源(代码和数据空间等资源,但是进程们之间的地址空间和资源是相互独立的),每个线程都有自己独立的运行栈和程序计数器(PC),JVM中的运行时数据区域关于线程私有领地
      • 换句话说,线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
      • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮
    • 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以共享访问隶属于进程的资源。
      • 每个独立的进程有程序运行的入口. 顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
    • 线程上下文切换比进程上下文切换要快得多。创建或撤销进程时,系统都要为之分配或回收系统资源,如内存空间,I/O设备等,OS所付出的开销显著大于在创建或撤销线程时的开销,进程切换的开销也远大于线程切换的开销
      • 线程之间切换的开销小。进程内的多个线程执行过程如下(多条线完成的,不是一条直线)
        在这里插入图片描述

    啧啧啧,算了,给你个赠品,补偿你一下
    在这里插入图片描述

  • 协程与线程的区别?

    • 线程和进程都是同步机制,而协程是异步机制
    • 线程是抢占式,而协程是非抢占式的。需要用户释放使用权切换到其他协程,因此**同一时间其实只有一个协程拥有运行权,相当于单线程的能力**。
    • 一个线程可以有多个协程,一个进程也可以有多个协程。
    • 协程不被操作系统内核管理,而完全是由程序控制。线程是被分割的CPU资源,协程是组织好的代码流程,线程是协程的资源。但协程不会直接使用线程,协程直接利用的是执行器关联任意线程或线程池
    • 协程能保留上一次调用时的状态
  • 僵尸进程与孤儿进程

    • 当咱们运行一个程序时,它会产生一个父进程以及很多子进程。 所有这些子进程都会消耗内核分配给它们的内存和 CPU 资源。这些子进程完成执行后会发送一个 Exit 信号然后死掉。这个 Exit 信号需要被父进程所读取。父进程需要随后调用 wait 命令来读取子进程的退出状态,并将子进程从进程表中移除。若父进程正确第读取了子进程的 Exit 信号,则子进程会从进程表中删掉。但若父进程未能读取到子进程的 Exit 信号,则这个子进程虽然完成执行处于死亡的状态,但也不会从进程表中删掉。【如果父进程先于子进程退出,子进程会成为孤儿进程。此时必须给子进程找到新的进程作为父进程,否则 当没有父进程的子进程退出时,因为没有父进程收尸,子进程会永远作为僵尸进程存在于系统中,浪费资源Linux内核对此的处理方法是在当前线程组中为子进程寻找一个线程作为父亲,如果不行(比如当前线程组没有其他线程),就让init做父进程init进程会例行地调用wait()来清除僵尸子进程。】
      • 通常进程的终结发生在exit()系统调用时,进程可能显示地调用这个系统调用,也可能隐式地调用(C编译器会在main函数return点后面插入exit语句)。
      • 打开终端并输入下面命令:ps aux | grep Z,会列出进程表中所有僵尸进程的详细内容。正常情况下我们可以用 SIGKILL,信号来杀死进程,但是僵尸进程已经死了, 你不能杀死已经死掉的东西。 因此你需要输入的命令应该是kill -s SIGCHLD pid。将这里的 pid 替换成父进程的进程 id,这样父进程就会删除所有以及完成并死掉的子进程了。

拜拜…
胡&敏:你这…
author:下期带你进入真正的多高(多线程与高并发),正的多高,的多高,多高,高…(一直荡漾着边跑跑喊的阵阵回声)

巨人的肩膀:
并发编程实战
并发编程之美
https://xiaolincoding.com/os/
B战各位老师

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值