进程/线程/协程的区别

1 基本概念

  • 进程(Process)

    进程是应用程序的启动实例,进程拥有代码和打开的文件资源、数据资源、独立的内存空间。

  • 线程(Lightweight Process,LWP)

    线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。
    对操作系统而言,线程是最小的执行单元,进程是最小的资源管理单元。无论是进程还是线程,都是由操作系统所管理的。

  • 协程(Coroutines)

    协程是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。

  • 进程、线程、协程的对比:

    (1)协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。

    (2)一个进程可以包含多个线程,一个线程可以包含多个协程。

    (3)一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。

    (4)协程与进程、线程一样,切换是存在上下文切换问题的。

  • 上下文切换对比:

    (1)进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。

    (2)线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。线程的切换内容包括内核栈和硬件上下文,线程切换内容保存在内核栈中.线程切换过程是由“用户态到内核态到用户态”,切换效率中等。因为线程的调度是在内核态运行的,而线程中的代码是在用户态运行,因此线程切换会导致用户态与内核态的切换

    (4)协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。

1.1 进程调度

MAX_RT_PRIO = 100

普通进程,SCHED_NORMAL调度策略 0~MAX_RT_PRIO-1, 即0~99

实时进程,SCHED_FIFO,或SCHED_PR调度策略 MAX_RT_PRIO~MAX_RT_PRIO+40, 即 100~140

linux根据进程优先级来进行调度,任何时候,实时进程的优先级都高于普通进程,实时进程只会被更高级的实时进程抢占,同级实时进程之间是按照FIFO(一次机会做完)或者RR(多次轮转)规则调度的。

  • 实时进程

    不同与普通进程,系统调度时,实时优先级高的进程总是先于优先级低的进程执行。直到实时优先级高的实时进程无法执行。实时进程总是被认为处于活动状态。如果有数个 优先级相同的实时进程,那么系统就会按照进程出现在队列上的顺序选择进程。不同调度策略的实时进程只有在相同优先级时才有可比性:

    1、对于FIFO的进程,意味着只有当前进程执行完毕才会轮到其他进程执行。由此可见相当霸道。

    2、对于RR的进程。一旦时间片消耗完毕,则会将该进程置于队列的末尾,然后运行其他相同优先级的进程,如果没有其他相同优先级的进程,则该进程会继续执行。

      总而言之,对于实时进程,高优先级的进程就是大爷。它执行到没法执行了,才轮到低优先级的进程执行。等级制度相当森严啊。

  • 普通进程

    Linux对普通的进程,根据动态优先级进行调度。而动态优先级是由静态优先级(static_prio)调整而来。Linux下,静态优先级是用户不可见的,隐藏在内核中。而内核提供给用户一个可以影响静态优先级的接口,那就是nice值,两者关系如下:

    static_prio=MAX_RT_PRIO +nice+ 20

    nice值的范围是-20至19,因而静态优先级范围在100至139之间。nice数值越大就使得static_prio越大,最终进程优先级就越低。

    动态优先级:
    dynamic_prio = max (100, min(static_prio - bonus + 5, 139)),
    奖励(bonus)根据进程的平均睡眠时间计算所得,取值范围为0至10。
    而进程的时间片就是完全依赖static_prio 定制的,见下图
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JgfYGcsV-1663468431680)(3E433F7A7AB14A00ADD3693BBC01B760)]

    操作系统在选取运行进程的时候按照最小的vruntime来的,虚拟时间的计算公式为虚拟运行时间 vruntime += 实际运行时间 delta_exec * NICE_0_LOAD/ 权重,其中权重就是优先级。这就是说,同样的实际运行时间,给高优先级的算少了,低优先级的算多了,但是当选取下一个运行进程的时候,还是按照最小的 vruntime 来的,这样高优先级的获得的实际运行时间自然就多了。
    参考链接

1.2 孤儿进程和僵尸进程

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

问题及危害:unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
参考链接

1.3 进程间的通信方式

  • 管道
    管道分为匿名管道和命名管道,匿名管道由pipe系统调用创建。创建后会有两个文件句柄,一个用于读,一个用于写。匿名管道一般用于父子进程间的通信,管道是单向通信的,要实现进程之间的双向通信需要创建两个管道。命名管道由mkfifo创建,命名管道就是FIFO,管道是先进先出的通讯方式。FIFO是一种先进先出的队列。它类似于一个管道,只允许数据的单向流动。其与匿名管道的一个重要区别是它提供了一个文件路径名与之关联,任何进程只要能访问该文件就能实现进程间的相互通信。容量有限,速度慢。
  • 消息队列
    消息队列是消息的连接表,存放在内核中并由消息队列标识符标识这种通信机制传递的数据具有某种结构,而不是简单的字节流。消息队列是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失。
  • 信号量
    信号量不能传递复杂消息,只能用来同步
  • 共享内存
    只要首先创建一个共享内存区,两个进程只要按照进程A用户空间-共享内存-进程B用户空间的步骤就可以对共享内存区中的数据进行读写。
    其中共享内存的效率最高,原因是共享内存的数据拷贝只有两次,即进程A的内存空间到共享内存,共享内存到进程B的内存空间。管道/消息队列的效率较低,原因是数据拷贝有四次。以消息队列为例,首先是进程A用户内存空间数据拷贝到A进程内核缓冲区,内核缓冲区数据拷贝到消息队列,进程B需要将消息队列数据拷贝到B进程内核缓冲区,最后将B进程内核缓冲区的数据拷贝到进程B的用户内存空间。
  • socket套接字
    套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

1.4 进程状态图

进程/线程/协程的区别_第1张图片

你可能感兴趣的:(Linux操作系统,linux)