关于进程、线程、协程的概念以及Java中的应用
进程、线程、协程
本文将从“操作系统”、“Java应用”上两个角度来探究这三者的区别。
一、进程
在我本人的疑惑中,我有以下3个问题。
1.1为什么要引入进程?
在“多道程序环境下”,允许多个程序并发执行,此时它们将失去封闭性,并具有间断性以及不可再现性的特征,因此需要引入进程的概念。
1.2什么是进程?进程由什么构成。
进程是程序执行的过程 ,包括了动态创建、调度和消亡的的整个过程,进程是程序资源管理的最小单位。好比下列汇编代码代码所示,包含对程序资源的调度。
_add_a_and_b:
push %ebx
mov %eax, [%esp+8]
mov %ebx, [%esp+12]
add %eax, %ebx
pop %ebx
ret
_main:
push 3
push 2
call _add_a_and_b
add %esp, 8
ret
进程由三部分组成。
- 进程控制块(PCB),是进程存在的唯一标识,
- 程序段
- 数据段
1.3Liunx进程实现
Linux 并没有为线程准备特定的数据结构,因为 Linux只有task_struct这一种描述进程的结构体。在内核看来只有进程而没有线程,线程调度时也是当做进程来调度的。Linux所谓的线程其实是与其他进程共享资源的轻量级进程。
为什么说是轻量级呢?在于它只有一个最小的执行上下文和调度程序所需的统计信息,它只带有进程执行相关的信息,与父进程共享进程地址空间
二、线程
2.1线程是什么?
线程是操作操作系统能够运行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运作的单位,一个进程内可以包含多个线程,线程是资源调度的最小单位。
在Liunx中,线程没有特定的数据结构,如何区别开来线程与进程的创建呢,这根据创建时参数的不同来决定,例如线程只有一个最小的执行上下文和调度程序所需的统计信息。
2.2线程拥有什么?
同一进程中的多条线程共享该进程中的 全部系统资源,如
- 虚拟地址空间
- 文件描述符号
- 信号处理
但是同一个进程的不同线程也有各自的信息,如
- 调用栈
- 寄存器环境
- 线程本次存储
3.3线程分类
3.4 Java如何启动一个线程
start– > start0 …..Native方法(由JVM中的c++调用)----> Liunx内核的 pthread_create方法
三、协程
3.1 既然有多线程操作,为什么还要引入协程?
操作系统在线程等待IO的时候,会阻塞当前线程,切换到其他线程,这样在当前线程等待IO的过程中,其他线程可以继续执行。这样操作在系统线程较少的时候没有多大问题,可是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。
而协程刚好可以解决上述的两个问题。
3.2 协程的概念
协程就是把原来每个线程分别负责的任务压缩到少量线程中,每个线程中用协程来实现原来线程级别的任务,因为包装了系统io导致协程内遇到io不会导致当前线程被挂起,以起到最大化利用时间片,减少线程调度开销的作用。
协程运行在线程之上,当一个协程运行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。
- 协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程
- 协程的切换在用户态完成,切换的代价比线程从用户态到内核台的代价小很多。
3.3 协程的注意事项
实际上协程并不是什么银弹,协程只有在等待IO的过程中才能重复利用线程,线程在等待IO的过程中会陷入阻塞状态。
假设协程运行在线程之上,并且协程调用了一个阻塞IO操作,这时候会发生什么?实际上操作系统并不知道协程的存在,它只知道线程,因此在协程调用阻塞IO操作的时候,操作系统会让线程进入阻塞状态,当前的协程和其它绑定在该线程之上的协程都会陷入阻塞而得不到调度,这往往是不能接受的。
3.4 Java中的应用--虚拟线程
Java 19 已经预2022年9月20日发布,虚拟线程是其中的一项预览功能。
虚拟线程是轻量级的线程,它们不与操作系统线程绑定,而是由 JVM 来管理。它们适用于“每个请求一个线程”的编程风格,同时没有操作系统线程的限制。我们能够创建数以百万计的虚拟线程而不会影响吞吐。这与 Go 编程语言(Golang)的协程(如goroutines)非常相似。
Thread.startVirtualThread(() -> {
System.out.println("Hello, Project Loom!");
});