MST
星途 面试题库

面试题:Go中Goroutine启动原理与线程启动对比

请阐述Go语言中Goroutine的启动过程,并与传统线程的启动过程进行对比,说明两者在资源消耗、启动速度方面的差异。
34.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

Goroutine启动过程

  1. 创建G结构体:Go运行时系统为每个Goroutine创建一个G结构体,该结构体包含了Goroutine的栈空间、程序计数器(PC)、Goroutine状态等信息。栈空间在创建时一般有一个较小的初始大小,会随着需求动态增长和收缩。
  2. 调度器管理:创建好的G结构体被放入调度器的全局或者局部队列中。Go语言的调度器采用M:N调度模型(多个Goroutine映射到多个操作系统线程),调度器的主要职责是管理G(Goroutine)、M(操作系统线程)和P(处理器,用于执行Goroutine的上下文)。
  3. 执行:当一个M(操作系统线程)获取到一个P(处理器)时,它会从P关联的本地队列或者全局队列中取出一个G来执行。执行过程中,Goroutine会按照其代码逻辑运行,遇到I/O操作、系统调用或者主动调用runtime.Gosched()等情况时,会让出P,使得调度器可以安排其他Goroutine执行。

传统线程启动过程

  1. 系统调用:在大多数操作系统中,通过系统调用(如pthread_create在POSIX系统中)来创建线程。这个系统调用会通知操作系统内核,内核为新线程分配内核资源,包括独立的栈空间、线程控制块(TCB,包含线程状态、寄存器值等信息)等。
  2. 线程初始化:内核初始化线程的上下文,设置栈指针、程序计数器等。初始化完成后,线程进入可运行状态,被放入操作系统的线程调度队列中。
  3. 调度执行:操作系统的调度器根据其调度算法(如时间片轮转、优先级调度等),从调度队列中选择线程并分配CPU时间片,线程开始执行其指定的函数。

资源消耗差异

  • 栈空间
    • Goroutine:初始栈空间通常较小(如2KB),且栈空间可动态增长和收缩,在不需要大量栈空间时,占用资源少。
    • 传统线程:栈空间一般在创建时就固定分配,通常较大(如几MB),即使实际使用的栈空间远小于分配的大小,也会占用该固定大小的内存,导致内存浪费。
  • 内核资源
    • Goroutine:运行在用户态,不需要内核为每个Goroutine分配专门的内核资源,多个Goroutine复用少量的操作系统线程(M),因此整体内核资源消耗少。
    • 传统线程:每个线程都需要内核分配独立的内核资源(如TCB等),随着线程数量的增加,内核资源消耗显著增加。

启动速度差异

  • Goroutine:创建过程只是在用户态进行一些简单的内存分配和数据结构初始化(创建G结构体等),不涉及系统调用,所以启动速度非常快,可以在短时间内创建大量的Goroutine。
  • 传统线程:创建需要通过系统调用进入内核态,内核进行资源分配和初始化等操作,系统调用开销较大,导致线程启动速度相对较慢,创建大量线程时性能开销明显。