1. 进程、线程和协程
1.1 进程
具体来说,进程的内存占用主要由以下几部分组成:
- 代码段(Code Segment):包括程序的指令、常量和只读数据等,通常大小为几 MB 到几十 MB 不等。
- 数据段(Data Segment):包括程序的静态变量和全局变量等可读写的数据,通常大小也为几 MB 到几十 MB 不等。
- 堆(Heap):动态分配的内存空间,用于存放程序运行期间申请的内存块,通常大小为几 MB 到几百 MB 不等。
- 栈(Stack):用于存放程序调用栈和局部变量等信息,通常大小为几 MB 到几十 MB 不等。
- 其他内存映射区域(Memory Mapped File):用于映射文件和设备等资源的内存空间,通常大小与映射的文件和设备大小相关。
1.2 线程
具体来说,线程的内存占用主要由以下几部分组成:
- 线程控制块(Thread Control Block, TCB):包括线程状态、线程上下文、堆栈指针等信息,通常大小为几 KB 到几十 KB 不等。
- 线程堆栈(Thread Stack):用于存放线程函数的调用栈和局部变量等信息,通常大小为几百 KB 到几 MB 不等。
- 线程相关的数据结构和资源:如线程私有变量、锁、条件变量等等,其大小与具体使用方式和应用场景相关。 在常见的协程库中,协程的内存占用主要由以下几部分组成:
1.3 协程
- 协程控制块(Coroutine Control Block, CCB):包括协程状态、协程上下文、栈指针等信息,通常大小为几十字节到几百字节不等。
- 协程堆栈(Coroutine Stack):用于存放协程函数的调用栈和局部变量等信息,通常大小为几 KB 到几十 KB 不等。
- 协程相关的数据结构和资源:如协程私有变量、锁、条件变量等等,其大小与具体使用方式和应用场景相关。
在操作系统中,进程是操作系统资源分配的单位;线程是处理器调度和执行的基本单位。
在 Linux 中,线程和进程都是通过系统调用来创建和管理的。线程通常是通过 pthread 库来创建和管理的,而进程则是通过 fork 系统调用来创建的。
| 特点 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 执行环境 | 拥有独立的虚拟地址空间、堆、栈、数据段和代码段等资源 | 与所属进程共享同一份地址空间和资源 | 与线程相似,但拥有更轻量级的执行环境 |
| 资源管理 | 每个进程有独立的资源管理机制,互不干扰 | 与所属进程共享同一份资源,可以共享数据和代码段等资源 | 与线程相似,但由程序自行管理和调度 ,虽然协程之间可以共享数据,但是并不会共享线程的资源,每个协程都有独立的栈空间和寄存器,因此协程之间是隔离的。 |
| 切换开销 | 切换时需要保存和恢复进程的所有上下文,包括进程的虚拟地址空间、堆、栈、寄存器和状态等,开销较大 | 切换时只需保存和恢复线程的栈、寄存器和状态等少量信息,开销较小 | 切换时只需保存和恢复协程的栈、寄存器和状态等少量信息,开销更小 |
| 创建和销毁开销 | 需要操作系统的介入,因为进程需要由操作系统来分配和管理资源,例如内存、文件描述符、信号量、共享内存等等,创建和销毁开销较大 | 可通过 pthread 库等实现,开销相对较小 | 由程序自行管理和调度,开销更小 |
| 并发处理能力 | 能够实现真正的并行处理,但开销大 | 可以实现并发处理,但需要考虑线程安全和同步机制 | 可以实现轻量级的协程并发处理,但需要考虑协程之间的调度和通信机制 |
| 应用场景 | 适用于需要独立执行和隔离资源的应用场景 | 适用于需要实现多任务和并发处理的应用场景 | 适用于需要实现高效的协作并发和异步处理的应用场景 |
注意:
- 相较于线程,协程的开销更小,因为协程的执行环境相对于线程更加轻量级。具体来说,协程相比线程少了以下开销:
- 内存开销:协程拥有自己的栈空间,但栈的大小可以根据实际需要进行调整(在大部分操作系统中,线程栈的大小是固定的,无法根据实际需要进行调整。线程栈的大小在创建线程时就已经确定了,通常是在编译时就指定了默认的大小,也可以在创建线程时手动指定大小),因此协程的内存开销比线程更小。
- 上下文切换开销:协程之间的切换只需要保存和恢复少量的状态信息(状态信息可以直接从寄存器中保存和加载,无需访问内存),如寄存器、栈指针等,而线程之间的切换需要保存和恢复整个执行上下文,包括程序计数器、寄存器、栈指针等,因此协程的上下文切换开销也比线程更小。
- 调度开销:协程的调度是由程序自行管理和调度的,而线程的调度是由操作系统内核进行管理和调度的,因此协程的调度开销也比线程更小。