目录
前言:
一、进程
进程的概念
进程内存空间
二、线程
线程的定义
内核线程
用户线程
内核线程和用户线程的比较
线程的状态
三、协程
协程的定义
协程序相对于线程优势
运用场景
四、线程、协程、进程切换比较
前言:
有时候无法理解进程、线程、以及协程的它们所存在的意义以及各有什么不同;同时如何深层次理解它们,才能在实际运用了能给我们多技术选择或方向。
一、进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体
进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的概念
第一:进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
第二:进程是一个实体。每一个进程都有它自己的地址空间。
进程内存空间
一般情况下,包括内核空间(Kernel space )、文本区域(text region或Code Segment)、数据区域(data region)和堆区(heap region)、BBS区域(bbs region )栈(stack region)。
内核区
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 的协作式多任务。
内核线程和用户线程的比较
线程的状态
初始化状态
产生一个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 操作执行完毕以后再收回其他函数的控制权.
四、线程、协程、进程切换比较
| 协程 | 线程 | 进程 |
切换着 | 用户(编程/程序本身) | 操作系统 | 操作系统 |
切换时机 | 用户自己的程序决定 | 根据操作系统自己策略调度,对于用户是透明的 | 根据操作系统自己策略调度,对于用户是透明的 |
切换内容 | 硬件上下文 | 内核栈 硬件上下文 | 页全局目录 内核栈 硬件上下文 |
切换内容的保存 | 保存用户自己设定的栈中 | 保存在内核栈中 | 保存在内核栈中 |
切换过程 | 不需要进入内核态进行切换 | 用户态-内核态-用户态 | 用户态-内核态-用户态 |
切换效率 | 高 | 中 | 低 |