1. 线程
在操作系统中,进程是分配资源的基本单位,但当进程作为调度的基本单位时,会造成较大的开销,频繁的进程调度将消耗大量时间。因此引出了线程:线程是处理器调度的基本单位,线程只拥有很小的运行时必要的资源。一个进程可拥有多个线程,同一个进程中的所有线程共享进程获得的主存空间和资源。
线程的实现
有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型,即实现用户级线程 和内核级线程的连接方式:多对一模型、一对一模型、多对多模型
。
2. goroutine
在Go语言中,每一个并发的执行单元叫作一个goroutine,是一种轻量级的线程。
3. 线程与goroutine的区别
运行时栈的大小
- 每个系统级线程都会有一个固定大小的栈(一般为2MB),主要用于保存函数递归调用时参数和局部变量。这造成了两个问题:
- 对于某些需要很小的栈空间的线程来说是一个巨大的浪费
- 对于少数需要巨大栈空间的线程来说又面临栈溢出的风险
- goroutine会以一个很小的栈启动(2KB或4KB),当遇到深度递归时导致当前栈空间不足,会根据需要动态的伸缩栈的大小。
- 每个系统级线程都会有一个固定大小的栈(一般为2MB),主要用于保存函数递归调用时参数和局部变量。这造成了两个问题:
调度
- go的运行时还包括了其自己的调度器,可以在n个操作系统线程上多工调度m个goroutine(类似于多线程模型中的多对多模型)。
- go调度器的工作和内核的调度时相似的,但是这个调度器只关注单独的go程序中的goroutine。
- goroutinie采用的是半抢占式的协作调度,只有当当前goroutine发生阻塞时才会导致调度。
- 这种调度发生在用户态,调度器会根据具体函数只保存必要的寄存器,切换的代价比系统线程要低得多。
创建和销毁
- Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。- goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。