在面试中,经常会有面试官问题“进程、线程和协程的区别”这个问题,这也是大学课程操作系统中最基本的知识。我们通常对此都说上几句,但细节又不是特别深入明了。我整理了一下相关的内容,加上自己的理解,与君共享。
1. 进程
1.1 定义
进程是计算机中程序的一次运行活动,是操作系统进行资源分配和调度的基本单位。每一个进程都拥有自己的地址空间,一般包括代码段、数据段、堆和栈。其中,代码段用来存放处理器执行的代码;数据段存放全局和静态变量;堆用来存放动态分配的内存;栈用来存放局部变量、函数参数和寄存器的值等。
1.2 进程切换
多个进程同时运行时,为了保证所有进程都能获得执行机会,就需要按照一定的算法不断地进行进程切换。所谓进程切换就是从运行中的进程中收回处理器,然后再使待运行进程来占用处理器。从某个进程收回处理器,实质上就是把进程运行过程中寄存器的中间数据存放到进程的堆栈。让某个进程来占用处理器,实质上是把这个进程存放在堆栈中的寄存器数据恢复到处理器的寄存器中去,并把待运行进程的断点送入处理器的程序计数器。
一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,所以进程切换就是被中止进程与待运行进程上下文的切换。进程切换上下文时,需要进出操作系统内核,并进行寄存器数据切换等工作,都需要一定的时间开销。
1.3 进程函数
-
pid_t fork(void)
功能:创建一个子进程
-
int clone(int (*fn)(void ), voidchild_stack, int flags, void *arg)
功能:复制一个子进程
-
pid_t getpid(void)
功能:获取自己的进程id
-
pid_t wait(int *status) or pid_t waitpid(pid_t pid, int *status, int options)
功能:阻塞父进程,直到子进程结束或者接收到指定的信号
2. 线程
2.1 定义
线程是进程中的一个实体,是处理器调度和分派的基本单位,它是比进程更小的能独立运行的单元。线程基本上不拥有独立的系统资源,它与同属一个进程的其他的线程共享进程拥有的资源,线程独自拥有少量的程序计数器、数据寄存器和栈等运行中必不可少的私有资源。因此,操作系统调度线程比进程付出的开销小得多,利用线程能够有效地提高系统的并发效率。
总之进程是资源分配的基本单位,线程是调度的基本单位。
2.2 共享资源
线程共享了所属进程的资源,包括:
- 内存地址空间
- 进程基础信息
- 打开的文件
- 信号处理
- 当前工作目录
- 用户和用户组属性
2.3 线程函数
-
pthread_create(tid,&attr,func,&arg)
功能:创建一个新的线程。
-
pthread_join(tid,void **retval)
功能:等待线程号为tid的线程执行结束后回收线程资源,类似于进程的wait()函数,阻塞进程。
-
pthread_exit(void *retval)
功能:结束线程,并返回结束码
-
pthread_cancel(tid)
功能:取消线程
-
pthread_self(void)
功能:获取线程id号
3. 协程
3.1 定义
协程本质上是一种用户态线程,它不需要操作系统来进行调度,而是由用户程序自行管理和调度。它寄存于线程中,系统开销极小,可以显著的提高性能和并发能力。使用协程的优点是运行效率高、编程简单、结构清晰;缺点是需要编程语言的支持,如果不支持,则需要在用户自行实现调度器。目前,原生支持协程的语言不是很多。
协程有自己的上下文,同属一个进程的协程共享进程拥有的系统资源。协程的切换由自己控制,由切换到其他协程由当前协程来控制。与线程和进程相比,协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭。
3.2 go语言的协程
go语言在语言级别支持协程,go的协程叫goroutine。go语言标准库提供的所有系统调用操作,都会出让 处理器给其它goroutine,这使得协程切换管理不再依赖于系统的线程和进程。
go协程与线程的性能比较:
-
内存消耗
goroutine:2KB
线程:8MB
-
调度切换开销
goroutine:只修改PC/SP/DX寄存器的值
线程:模式切换用户态和内核态之间的模式切换)、所有寄存器的刷新
通过关键字go 就启动了一个 goroutine。
package main
import ( ·
"fmt"
"time"
)
func echo(s string) {
fmt.Println(s)
}
func main() {
// 启动两个协程(goroutine)
go echo("Hello world 1")
go echo("Hello world 2")
// 阻塞3秒,等待协程执行完毕
time.Sleep(3*time.Second)
}