[Java进阶] 并发编程之进程、线程和协程
并发编程系列文章
[Java进阶] 并发编程入门介绍
目录
定义与概念
进程
状态
生命周期管理
线程
状态
生命周期管理
协程
状态
生命周期管理
进程、线程、协程对比
资源开销
上下文切换
并行与并发
隔离性
编程复杂度
定义与概念
- 进程:进程是程序在执行过程中分配的基本单位,是程序的一次动态执行过程。每个进程都有独立的内存空间和系统资源,通过进程间通信(IPC Inter-Process Communication)进行通信。
- 线程:线程是进程中的一个执行路径或执行单元,是CPU调度的最小单位。线程共享进程的资源,因此线程间通信相对简单且高效。
- 协程:协程是一种用户态的轻量级线程,其调度和切换完全由用户控制。协程不是由操作系统内核管理,而是由程序所控制,适合处理大量的I/O密集型任务。
进程
状态
- 新建(New):进程正在被创建,但尚未准备好执行。操作系统分配必要的资源,如内存、文件描述符等,并创建进程控制块(PCB)。
- 就绪(Ready):进程已经准备好执行,等待CPU资源。进程在就绪队列中等待调度器分配CPU时间。
- 运行(Running):进程正在CPU上执行。操作系统调度器选择一个就绪状态的进程,将其状态变为运行,并分配CPU资源。
- 阻塞(Blocked):进程因为等待某个事件(如I/O操作完成、信号量等)而暂停执行。进程从运行状态变为阻塞状态,释放CPU资源,直到等待的事件发生。
- 终止(Terminated):进程执行完毕或因错误而终止。操作系统释放该进程占用的资源,删除其PCB。
生命周期管理
- 创建:
- 系统调用:用户或系统通过系统调用(如
fork()
、exec()
)请求创建新进程。 - 资源分配:操作系统分配必要的资源,如内存、文件描述符等。
- 进程控制块(PCB):创建进程控制块(PCB),记录进程的状态、优先级、内存分配情况等信息。
- 系统调用:用户或系统通过系统调用(如
- 调度:
- 调度算法:操作系统使用调度算法(如时间片轮转、优先级调度、最短作业优先等)选择下一个要执行的进程。
- 调度器:调度器负责将CPU分配给选中的进程,并保存当前进程的上下文(寄存器状态、程序计数器等)到PCB。
- 上下文切换:
- 保存上下文:当当前进程因某种原因(如时间片用完、等待I/O操作、被更高优先级的进程抢占等)需要暂停执行时,操作系统会进行进程切换。
- 更新状态:将当前进程的状态从“运行”改为“就绪”或“阻塞”。
- 选择新进程:根据调度算法选择下一个要执行的进程。
- 恢复上下文:从新进程的PCB中恢复其上下文。
- 切换控制权:修改程序状态字(PSW),将控制权交给新的进程。
- 终止:
- 执行完毕或错误:进程执行完毕或因错误而终止。
- 资源释放:操作系统释放该进程占用的资源,删除其PCB。
线程
状态
- 新建(New):线程正在被创建,但尚未准备好执行。操作系统分配必要的资源,如栈空间,并创建线程控制块(TCB)。
- 就绪(Ready):线程已经准备好执行,等待CPU资源。线程在就绪队列中等待调度器分配CPU时间。
- 运行(Running):线程正在CPU上执行。操作系统调度器选择一个就绪状态的线程,将其状态变为运行,并分配CPU资源。
- 阻塞(Blocked):线程因为等待某个事件(如I/O操作完成、信号量等)而暂停执行。线程从运行状态变为阻塞状态,释放CPU资源,直到等待的事件发生。
- 终止(Terminated):线程执行完毕或因错误而终止。操作系统释放该线程占用的资源,删除其TCB。
生命周期管理
- 创建:
- 系统调用:在已存在的进程中通过系统调用(如
pthread_create()
)创建新线程。 - 资源分配:操作系统分配线程所需的少量资源(如栈空间)。
- 线程控制块(TCB):创建线程控制块(TCB),记录线程的状态、优先级等信息到TCB。
- 系统调用:在已存在的进程中通过系统调用(如
- 调度:
- 调度算法:操作系统使用线程调度算法(如时间片轮转、优先级调度等)选择下一个要执行的线程。
- 调度器:调度器负责将CPU分配给选中的线程,并保存当前线程的上下文。
- 上下文切换:
- 保存上下文:当当前线程因某种原因(如时间片用完、等待I/O操作、被更高优先级的线程抢占等)需要暂停执行时,操作系统会进行线程切换。
- 更新状态:将当前线程的状态从“运行”改为“就绪”或“阻塞”。
- 选择新线程:根据调度算法选择下一个要执行的线程。
- 恢复上下文:从新线程的TCB中恢复其上下文。
- 切换控制权:修改程序状态字(PSW),将控制权交给新的线程。
- 终止:
- 执行完毕或错误:线程执行完毕或因错误而终止。
- 资源释放:操作系统释放该线程占用的资源,删除其TCB。
协程
状态
- 新建(New):协程正在被创建,但尚未准备好执行。应用程序或库分配必要的资源,如栈空间,并创建协程控制块(CCB)。
- 就绪(Ready):协程已经准备好执行,等待调度。协程在就绪队列中等待调度器分配执行机会。
- 运行(Running):协程正在执行。调度器选择一个就绪状态的协程,将其状态变为运行,并分配执行机会。
- 挂起(Suspended):协程因为等待某个事件(如I/O操作完成、其他协程的通知等)而暂停执行。协程从运行状态变为挂起状态,释放执行资源,直到等待的事件发生。
- 终止(Terminated):协程执行完毕或因错误而终止。应用程序或库释放该协程占用的资源,删除其CCB。
生命周期管理
- 创建:
- 库函数:应用程序或库通过特定的库函数(如Python的
asyncio.create_task()
、C++的std::coroutine_handle
)创建新协程。 - 资源分配:应用程序或库分配必要的资源(如栈空间)。
- 协程控制块(CCB):创建协程控制块(CCB),记录协程的状态、栈指针等信息到CCB。
- 库函数:应用程序或库通过特定的库函数(如Python的
- 调度:
- 调度器:使用协程调度器(如Python的
asyncio
、C++的std::coroutine_handle
)选择下一个要执行的协程。 - 调度算法:调度器通常使用简单的调度算法,如基于事件循环的调度。
- 调度器:使用协程调度器(如Python的
- 上下文切换:
- 保存上下文:当当前协程因某种原因(如等待I/O操作、其他协程的通知等)需要暂停执行时,应用程序或库会进行协程切换。
- 更新状态:将当前协程的状态从“运行”改为“挂起”。
- 选择新协程:根据调度算法选择下一个要执行的协程。
- 恢复上下文:从新协程的CCB中恢复其上下文。
- 切换控制权:修改程序状态字(PSW),将控制权交给新的协程。注意,协程的上下文切换完全在用户态进行,不涉及内核态和用户态的转换。
- 终止:
- 执行完毕或错误:协程执行完毕或因错误而终止。
- 资源释放:应用程序或库释放该协程占用的资源,删除其CCB。
进程、线程、协程对比
下面将从资源开销、上下文切换、并行与并发、隔离性和编程复杂度五个方面全面对比进程、线程和协程。
资源开销
- 进程:由于每个进程都有独立的内存空间和系统资源,因此创建和销毁进程的开销较大。
- 线程:线程共享进程的资源,因此创建和销毁线程的开销相对较小。但仍需操作系统进行调度和上下文切换。
- 协程:协程的创建和销毁开销极小,通常仅涉及函数调用和栈空间分配。协程的调度和切换也完全在用户态进行,因此开销极低。
上下文切换
- 进程:进程上下文切换涉及内核态和用户态的转换,包括切换页表、内核栈等,因此开销较大。
- 线程:如果线程属于不同的进程,同样涉及内核态和用户态的转换。如果线程属于同一进程,则内核态转换的开销相对较小,但仍需在内核中完成上下文切换。
- 协程:协程的上下文切换完全在用户态进行,不涉及内核态切换,因此开销最小。
并行与并发
- 进程:支持真正的并行运行,多个进程可以在多核CPU上同时执行。
- 线程:同样支持真正的并行,多个线程可以在多核CPU上并行执行。但共享进程资源,可能引发竞争条件。
- 协程:协程本质上是并发的,而非并行。因为协程在单个线程内运行,因此无法利用多核CPU的并行能力。
隔离性
- 进程:进程之间有独立的内存地址空间、文件描述符等资源,彼此完全隔离,适合需要强隔离的任务。
- 线程:同一进程内的线程共享地址空间、全局变量和文件描述符,因此线程之间资源隔离较弱,容易出现数据竞争。
- 协程:协程共享同一线程的所有资源,同一线程内的协程没有隔离。但因为协程在用户态中切换,通常不会与其他协程并发执行。
编程复杂度
- 进程:编写多进程程序较为复杂,特别是在进程间通信(IPC)时需要考虑复杂的机制。
- 线程:多线程程序编写也较为复杂,需处理同步和互斥问题,以防止数据竞争。
- 协程:协程编写相对简单,特别是在异步框架中,代码结构更加清晰。但需要注意手动调度和避免阻塞操作。