美文网首页
进程、线程、协程之间的演变

进程、线程、协程之间的演变

作者: 孤縌 | 来源:发表于2023-03-14 11:47 被阅读0次

    1.进程

    1.1定义

    进程是计算机中一个正在执行的程序实例。进程是操作系统中最基本的执行单元,它拥有自己的程序代码、数据集合和执行状态,并由操作系统管理和调度。

    1.2内容

    进程通常由程序、数据集合和进程控制块三部分组成。

    1. 程序,是进程的代码部分,它是计算机程序的执行体现。程序通常由指令和数据组成,它们被存储在进程的内存空间中,并由 CPU 执行。

    2. 数据集合包括进程所需的所有数据,例如变量、数组、对象等。数据集合通常也被存储在进程的内存空间中,它们可以被程序访问和修改。

    3. 进程控制块(Process Control Block,PCB)是操作系统用来管理进程的数据结构。它包含了进程的状态、进程标识符、程序计数器、内存管理信息、进程优先级等信息。操作系统可以使用进程控制块来管理和调度进程,例如创建和终止进程、切换进程的执行等。

    1.3特征

    特征主要用于解释

    1. 动态性:进程是一个动态的概念,它代表了一个正在执行的程序实例,可以创建、调度和终止。在操作系统中,进程的创建和销毁是动态进行的。

    2. 并发性:在操作系统中,可以同时存在多个进程在执行,它们之间是并发执行的。操作系统可以通过进程调度算法来协调多个进程之间的执行,从而实现对系统资源的高效利用。

    3. 独立性:进程是操作系统中的独立执行单元,每个进程都有自己独立的内存空间、资源和执行状态,不会受到其他进程的影响。

    4. 异步性:不同进程之间的执行速度是不同的,它们可以相互竞争系统资源,例如 CPU、内存、I/O 设备等。进程之间的执行顺序是不可预测的,需要通过操作系统的调度算法进行管理。

    5. 共享性:进程之间可以共享系统资源,例如文件、消息队列、信号量等。操作系统提供了各种机制来协调进程之间的资源共享,从而实现对系统资源的高效利用。

    1.4梳理

    image.png

    2.线程

    2.1定义

    线程是操作系统中能够被调度的最小执行单元。一个进程可以包含多个线程,每个线程都是独立的执行流程,拥有自己的程序计数器、栈和局部变量等线程上下文信息,但共享进程的代码、数据和全局变量等资源。

    2.2由来

    任务调度:任务调度是操作系统中的一种机制,它用于协调和管理系统中的各种任务或进程的执行顺序和优先级。任务调度器会根据一定的策略,将CPU时间片分配给需要执行的任务或进程,以实现高效的系统资源利用和响应性能。

    在任务调度的过程中,操作系统会根据不同的调度算法来确定任务的执行顺序和优先级。常见的调度算法包括先来先服务(FCFS)、最短作业优先(SJF)、优先级调度、时间片轮转等。

    在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了,于是就发明了线程。

    2.3内容

    线程通常由线程 ID、程序计数器、寄存器集合、栈和状态等组成。

    1. 线程 ID:每个线程都有一个唯一的线程 ID,用于区分不同的线程。

    2. 程序计数器:程序计数器是一个指针,用于记录当前线程执行的位置。当线程被暂停或切换时,程序计数器的值会保存到线程控制块中,以便下次恢复执行。

    3. 寄存器集合:线程也拥有自己的寄存器集合,用于保存线程的局部变量和其他状态信息。

    4. 栈:每个线程都有自己的栈,用于保存函数调用时的参数、返回地址和局部变量等信息。线程栈通常比进程栈小得多,因为线程不需要保存进程的所有状态信息。

    5. 状态:线程可以处于就绪、运行、等待或结束等不同的状态。就绪状态表示线程可以立即执行,等待状态表示线程需要等待某些条件满足才能执行,运行状态表示线程正在执行,结束状态表示线程已经完成执行。

    java线程状态图:线程状态图-参考博客

    线程状态图

    2.3.1Java栈

    Java栈是Java虚拟机中的一种重要数据结构,用于存储方法调用的信息,包括局部变量、方法参数、返回值等。每个线程在运行时都会有一个对应的Java栈。

    Java栈中的每个元素称为栈帧(Stack Frame),用于保存方法调用的上下文信息。栈帧中包含了局部变量表、操作数栈、返回地址和异常处理表等信息。

    栈的数据结构

    图片来源

    2.3.2帧栈

    寄存器的使用

    在x86架构的CPU中,栈通常从高地址向低地址增长。通用寄存器(General Purpose Registers):包括eax、ebx、ecx、edx、esi、edi、ebp、esp等8个寄存器。

    1. ebp寄存器(Extended Base Pointer Register):通常用于指向当前栈帧的基地址(Base Address)

    2. esp寄存器(Extended Stack Pointer Register):通常用于指向栈顶指针

    3. ebx寄存器(Extended Base Register):通常用作指针,保存内存地址,也用于存储函数调用的返回值。

    4. edi寄存器(Destination Index Register):通常用于存储目标地址,例如字符串复制等操作。

    5. esi寄存器(Source Index Register):通常用于存储源地址,例如字符串比较等操作。

    操作流程:

    调用方法时,会将当前栈帧压入虚拟机栈中,然后创建新的栈帧,并将ebp和esp寄存器设置为新栈帧的基地址和栈顶指针。

    返回方法时,会弹出当前栈帧,并将ebp和esp寄存器恢复为上一个栈帧的基地址和栈顶指针。

    参考博客

    int main()
    {
        int a = 10;
        int b = 20;
        int c = 0;
        c = max(a, b);
        printf( "%d\n", c);
        system( "pause");
        return 0;
    }
    int max(int x, int y)
    {
        int z = 0;
        if (x > y)
            z = x;
        else
            z = y;
        return z;
    }
    

    执行顺序:

    1.esp和ebp分别先指向main()的栈帧基地址和栈顶

    2.从栈底向栈顶方向压入,分别是参数、返回地址、main的返回地址、main的ebp基地址

    3.最后栈顶会分别压入ebx、esi、edi寄存器


    栈帧中包含了局部变量表、操作数栈、返回地址和异常处理表等信息。

    栈帧内容

    栈帧中包含了局部变量表、操作数栈、返回地址和异常处理表等信息。

    1. 局部变量表:用于存储方法中定义的局部变量,包括基本数据类型和对象引用等,以索引的方式访问,从0开始。

    2. 操作数栈:用于存储操作数和返回值,通常使用先进先出(FIFO)的方式进行操作。

    3. 返回地址:用于保存方法调用完成后返回的地址,以便程序跳转回原来的调用点继续执行。

    4. 异常处理表:用于保存方法执行过程中可能抛出的异常处理信息,以便在发生异常时进行处理。

    操作数栈补充:

    Java虚拟机规范中规定,每个操作数栈的最大深度不能超过255个栈单元。如果超过了这个限制,将会抛出StackOverflowError异常。操作数栈的大小受到Java虚拟机内存分配的限制,如果内存不足,也会抛出OutOfMemoryError异常。

    复现方式:无限递归回调

    2.4梳理

    3.协程

    3.1定义

    协程,英文Coroutines,是一种基于线程之上,比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。

    概念补充:

    • 内核线程:由操作系统内核创建和管理的一种线程,即跟CPU的物理核数相关

    • 超线程技术(Hyper-Threading Technology):是一种基于硬件的多线程技术,将单个物理处理器中增加一个线程调度器和一组寄存器,用于存储多个线程的上下文信息,以此实现并行执行多个线程的效果。

    3.2由来

    多线程的缺点

    1. 复杂度增加:多线程编程需要考虑线程之间的同步、互斥、协作等问题,如锁、信号量、条件变量等,增加了程序的复杂度和开发难度。

    2. 死锁和竞态条件:多线程之间共享内存区域,容易发生死锁、竞态条件等问题,导致程序的不稳定性和异常终止。

    3. 资源消耗增加:每个线程都需要一定的系统资源,如内存、文件句柄、网络连接等,大量的线程会导致系统资源的耗尽和浪费。

    4. 上下文切换开销:线程之间的切换需要进行上下文的保存和恢复,会增加系统开销和延迟,影响程序的性能和响应速度。

    协程的优点

    1. 快速切换:相比线程,协程没有自己的调度器和上下文切换,协程通常采用生成器(Generator)或异步生成器(Async Generator)实现快速切换。生成器是一种特殊的函数,可以通过yield关键字暂停函数的执行,并返回一个中间结果,然后在需要的时候重新开始执行。异步生成器是一种特殊的生成器,可以在异步环境下使用,例如在事件循环中实现异步操作。

    2. 开销轻量:线程的默认Stack大小是1M,而协程接近1K,因此可减少多线程的系统资源开销。

    3. 低复杂度:线程的复杂度访问共有内存数据时,需要考虑锁、信号量、条件变量等,由此衍生出死锁和各种竞争状态,协程由于生成器是一种特殊的等待函数,可实现同步返回和恢复执行,且异步生成器则是异步返回和恢复执行,由此解决多线程网络通信、I/O操作等的死锁和竞争问题。

    3.3原理

    • 生成器:一种同步的生成器,它通过yield关键字来逐步生成数据。在生成器函数中,当执行到yield语句时,生成器会将当前状态保存下来,并将yield语句后面的值返回给调用方。当调用方需要获取下一个元素时,生成器会恢复之前保存的状态,并从上一次的yield语句处继续执行,生成下一个元素。生成器的应用场景主要是处理大量数据的场景,例如数据分页、数据转换等。

    • 异步生成器:异步生成器是基于异步迭代器(Async Iterator)实现的,可以处理异步的数据源。当异步生成器需要生成下一个元素时,它会暂停当前的协程执行,等待异步数据源返回数据后再生成下一个元素。异步生成器的应用场景主要是处理异步数据源、网络通信等场景。

    协程最重要的特点是支持挂起和恢复操作。在协程中,可以通过挂起函数将协程挂起,并将协程的状态保存下来,以便后续恢复执行。协程的挂起和恢复是由程序员手动控制的,因此可以更灵活地控制程序的执行顺序和并发度。

    3.4梳理


    博客参考:

    https://www.cnblogs.com/Survivalist/p/11527949.html

    相关文章

      网友评论

          本文标题:进程、线程、协程之间的演变

          本文链接:https://www.haomeiwen.com/subject/oaahrdtx.html