进程、线程、协程详解

目录

前言:

一、进程

进程的概念

进程内存空间

二、线程

线程的定义

内核线程

用户线程

内核线程和用户线程的比较

线程的状态

 三、协程

协程的定义        

协程序相对于线程优势

运用场景

四、线程、协程、进程切换比较


前言:

     有时候无法理解进程、线程、以及协程的它们所存在的意义以及各有什么不同;同时如何深层次理解它们,才能在实际运用了能给我们多技术选择或方向。


一、进程

        进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体

进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程的概念

第一:进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。

第二:进程是一个实体。每一个进程都有它自己的地址空间。

进程内存空间

一般情况下,包括内核空间(Kernel space )、文本区域(text regionCode Segment)、数据区域(data region)和堆区(heap region)、BBS区域(bbs region )栈(stack region)。 

d0de7e30f5044488a14330eded3d1839.png

内核区

        Kernel space 是操作系统内核的运行空间。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。在Kernel space中可以执行任意命令,调用系统的一切资源,如对文件大操作和socket 句柄等操作,属于内核操作。这一块区域用户不能直接更改,用户只能通过内核接口来访问。

栈区

        从高地址向低地址增长。由编译器自动管理分配。程序中的局部变量、函数参数值、返回变量等存在此区域。

内存映射区

         其实也属于堆区,只不过这一部分可以通过 mmap 来产生映射

未初始化数据区(bbs region)

        当全局/静态变量没有被初始化时,会放在该区域。处于BSS段的变量的值默认为0,考虑到

 这一点,BSS段内部无需存储大量的零值,而只需记录字节个数即可。系统载入可执行程序

 后,将BSS段的数据载入数据段(Data Segment) ,并将内存初始化为0,再调用程序入口(main函数)。     

数据区

        对于已经初始化了的全局/静态变量存放的内存区域。

文本区

        对于已经初始化了的全局/静态变量所存放的区域。

Heap区

        从低地址向高地址增长。容量大于栈,程序中动态分配的内存在此区域。如new和malloc申请的内存。


二、线程

线程的定义

线程是操作系统能够进行运调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程分为内核线程和用户线程。

内核线程

        由内核管理的线程。用户应用程序通过API和系统调用(system call)来访问线程工具。

需要内核的参与,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部需求进行创建和撤销。内核线程的线程表(thread table)位于内核中,包括了线程控制块(TCB),一旦线程阻塞,内核会从当前或者其他进程(process)中重新选择一个线程保证程序的执行。

用户线程

指不需要内核支持而在中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这样的操作系统中也可实现,但线程的调度需要用户程序完成,这有些类似 Windows 3.x 的协作式多任务。

内核线程和用户线程的比较

        720c4ab6db05419bbd4c681fc695ec0c.png

线程的状态

初始化状态

        产生一个Thread对象就生成一个新线程。当线程处于"新线程"状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。    

可运行态(Runnable)

        start()方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run()方法。在这时线程处于可运行态。

阻塞/非运行态(Not Runnable)

        suspend()方法被调用;

        sleep()方法被调用;

        线程使用wait()来等待条件变量;

        线程处于I/O请求的等待。

死亡态(Dead)

        当run()方法返回,或别的线程调用stop()方法,线程进入死亡态。


 三、协程

协程的定义        

协程(coroutine)是一种程序运行的方式,即在单线程里多个函数并发地执行。它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。协程是以单线程的执行方式执行。      

        为什么需要协程?因为对于线程所占用系统资源比较多,默认情况下,线程需要占用比较大空间同时需要线程调度,它是系统级别的。由于协程的暂停完全由程序控制,发生在用户态上;而线程的阻塞状态是由操作系统内核来进行切换,发生在内核态上。用户无法创建巨量的线程来处理业务逻辑,对于协程的数量远远大于线程的创建数量。

协程序相对于线程优势

1)因此协程的开销远远小于线程的开销。

        协程涉及到函数的切换, 多线程涉及到线程的切换, 所以都有执行上下文, 但是协程不是被操作系统内核所管理, 而完全是由程序所控制(也就是在用户态执行), 这样带来的好处就是性能得到了很大的提升, 不会像线程那样需要在内核态进行上下文切换来消耗资源。


2)线程是可能并行,协程是单线程的

        同一时间, 在多核处理器的环境下, 多个线程是可以并行的,但是运行的协程的函数却只能有一个,其他的协程的函数都被suspend, 即协程是并发的。


3)没有非同步问题(无锁)

        由于协程在同一个线程中, 所以不需要用来守卫临界区段的同步性原语(primitive)比如互斥锁、信号量等,并且不需要来自操作系统的支持


4)用户级别的调度和阻塞

        在协程之间的切换不需要涉及任何系统调用或任何阻塞调用

5)执行权利有程序分配
        通常的线程是抢先式(即由操作系统分配执行权), 而协程是由程序分配执行权

6)协程所占资源小于线程

        在默认情况下线程所占用堆栈空间8M的内存空间,而协程只需要几KB的空间。

运用场景

协程的应用场景主要在于 I/O 密集型任务。

协程调用是在一个线程内进行的,是单线程,切换的开销小,因此效率上略高于多线程;

当程序在执行 I/O 时操作时,CPU 是空闲的,此时可以充分利用 CPU 的时间片来处理其他任务;

有了协程,我们在函数的执行过程中,如果遇到了I/O密集型任务,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回其他函数的控制权.


四、线程、协程、进程切换比较

 

协程

线程

进程

切换着

用户(编程/程序本身)

操作系统

操作系统

切换时机

用户自己的程序决定

根据操作系统自己策略调度,对于用户是透明的

根据操作系统自己策略调度,对于用户是透明的

切换内容

硬件上下文

内核栈

硬件上下文

页全局目录

内核栈

硬件上下文

切换内容的保存

保存用户自己设定的栈中

保存在内核栈中

保存在内核栈中

切换过程

不需要进入内核态进行切换

用户态-内核态-用户态

用户态-内核态-用户态

切换效率


猜你喜欢

转载自blog.csdn.net/lejian/article/details/129236886