Golang的goroutine原理介绍

简介: Golang的goroutine原理介绍

Golang的goroutine介绍



当谈到Golang(又名Go)的特色时,一个特别突出的话题就是它的goroutine。Goroutine是一种轻量级的线程,Golang的并发模型是基于它们构建的。在本篇文章中,我们将会深入探究Golang的goroutine是如何实现的。


Goroutine的简介


Goroutine是一种比传统线程更加轻量级的并发处理方式。每一个Goroutine都是由Golang的运行时(runtime)所管理的,它们在同一个进程中运行,但是它们却可以同时执行不同的任务。因为Goroutine的轻量级,一个程序可以启动成百上千个Goroutine来执行任务,而不会对程序的性能产生太大的影响。


Goroutine的实现原理


在Golang中,goroutine的实现是通过运行时(runtime)来完成的。当我们创建一个goroutine时,runtime会帮我们在操作系统线程中启动一个goroutine并分配一个栈(Stack)来保存它的执行状态。


当一个goroutine被创建时,它会被加入到Golang的调度器(Scheduler)中,这个调度器是由runtime来管理的。调度器会决定哪个goroutine可以运行,哪个goroutine需要暂停等等。

Golang的调度器采用的是M:N的调度模型。也就是说,它将M个goroutine(M是操作系统线程的数量)映射到N个操作系统线程上。当一个goroutine需要执行时,调度器会从空闲的线程池中选取一个线程来执行这个goroutine。


调度器会监视每个goroutine的执行状态,如果一个goroutine阻塞了(比如等待I/O操作的完成),那么调度器会将这个goroutine暂停并执行另一个可用的goroutine。当被阻塞的goroutine再次可用时,调度器会再次将它放回运行队列中。


Goroutine的底层数据结构


在 Golang 中,每个 goroutine 是由 G 对象表示的,它包含了 goroutine 的栈、goroutine 的状态以及相关的信息。在 Golang 的运行时中,还有其他的结构体和数据结构来支持 goroutine 的实现和调度。


下面是 goroutine 的底层数据结构:


G (goroutine) 结构体:表示一个 goroutine。它包含了 goroutine 的栈、指令指针、程序计数器、状态、等待队列等信息。其中,状态字段包括以下几种状态:
_Gidle:空闲状态,等待分配任务。
_Grunnable:可运行状态,等待被调度执行。
_Grunning:正在执行状态,即当前正在运行的 goroutine。
_Gsyscall:系统调用状态,等待系统调用返回。
_Gwaiting:等待状态,等待某个事件(比如 channel 的读写、锁的释放等)。
_Gdead:死亡状态,goroutine 已经完成任务或被取消。


通过示例展示部分核心源码


以下是一个简单的 Go 程序,其中包含了创建和运行 goroutine 的代码。我们可以结合该程序的源码来展示 goroutine 的实现。


package main
import (
 "fmt"
 "time"
)
func main() {
 go sayHello() // 创建一个新的 goroutine 来执行 sayHello 函数
 time.Sleep(1 * time.Second)
 fmt.Println("Main goroutine exit.")
}
func sayHello() {
 for i := 0; i < 5; i++ {
  fmt.Println("Hello", i)
  time.Sleep(100 * time.Millisecond)
 }
}


在上述代码中,我们通过 go 关键字创建了一个新的 goroutine 来执行 sayHello 函数。这个 goroutine 会和主 goroutine 并发运行,互不干扰。


现在让我们来看一下 go 关键字的源码实现。在 Go 源码中,go 关键字的实现实际上是一个函数调用,该函数的定义如下:


func goFunc(fn *funcval, argp unsafe.Pointer, n int, callerpc uintptr)


该函数接受四个参数:


fn:函数指针,表示需要执行的函数。
argp:参数指针,表示函数的参数。
n:参数个数,表示函数的参数个数。
callerpc:调用者 PC 寄存器的值,用于调试和跟踪。


在函数内部,Go runtime 会创建一个新的 G 对象,并将该 G 对象的状态设置为 Grunnable,然后将其插入到某个 P 的本地队列或全局队列中等待被调度。


下面是 go 关键字的简化版源码实现:


func goFunc(fn *funcval, argp unsafe.Pointer, n int, callerpc uintptr) {
 newG := getg() // 获取当前 goroutine 的 G 对象
 if newG == oldG {
  newG = newproc() // 创建一个新的 P,并启动一个新的 M 来执行该 P
 }
 newG.sched.pc = callerpc // 设置新 goroutine 的调用者 PC 寄存器
 newG.sched.sp = uintptr(argp) // 设置新 goroutine 的堆栈指针
 newG.sched.argp = argp // 设置新 goroutine 的参数指针
 newG.sched.arglen = uintptr(n) // 设置新 goroutine 的参数个数
 newG.sched.fn = fn // 设置新 goroutine 需要执行的函数
 if atomic.Cas(&sched.runqsize, sched.runqsize, sched.runqsize+1) {
  lock(&sched.lock) // 获取全局调度器锁
  if sched.runqtail == nil {
   sched.runqhead = newG
   sched.runqtail = newG
  } else {
   sched.runqtail.sched.link = newG
   sched.runqtail = newG
  }
  unlock(&sched.lock) // 释放全局调度器锁
 }
}


Goroutine的优点


相较于传统线程的实现方式,Goroutine具有以下几个优点:


Goroutine是轻量级的。由于它们是由Golang的运行时管理的,所以它们的创建和销毁的代价非常小,可以轻松地创建大量的goroutine来完成任务。


Goroutine的切换代价低。由于Golang的调度器是基于协作式的调度模型,所以当一个goroutine被阻塞时,调度器可以立即切换到另一个可用的goroutine,而不需要等待操作系统的线程切换。


Goroutine具有内置的同步机制。Golang提供了一系列内置的同步机制(比如channels),可以用来协调不同的goroutine之间的通信和同


小结


总的来说,Golang 的 goroutine 是基于协作式的调度模型实现的,它通过调度器来分配不同的 goroutine 执行任务,从而实现并发。同时,Golang 还提供了丰富的内置函数和工具来协调不同 goroutine 之间的通信和同步。

相关文章
|
6月前
|
存储 监控 Linux
Golang 语言的 goroutine 调度器模型 GPM
Golang 语言的 goroutine 调度器模型 GPM
43 0
|
6月前
|
测试技术 Go 开发工具
100天精通Golang(基础入门篇)——第3天:Go语言的执行原理及常用命令、编码规范和常用工具
100天精通Golang(基础入门篇)——第3天:Go语言的执行原理及常用命令、编码规范和常用工具
148 1
|
2天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
13 1
|
1月前
|
程序员 Go 调度
第十六章 Golang中goroutine和channel
第十六章 Golang中goroutine和channel
28 3
|
6月前
|
Go
Golang 语言怎么控制并发 goroutine?
Golang 语言怎么控制并发 goroutine?
18 0
|
8月前
|
Go 调度 数据安全/隐私保护
Golang 并发&同步的详细原理和使用技巧
Golang 并发&同步的详细原理和使用技巧
|
6月前
|
存储 SQL 安全
Golang 语言标准库 context 包控制 goroutine
Golang 语言标准库 context 包控制 goroutine
29 0
|
6月前
|
Linux Go 调度
【Golang源码分析】golang的启动原理
【Golang源码分析】golang的启动原理
37 0
|
8月前
|
缓存 Go
Golang Channel 详细原理和使用技巧
Golang Channel 详细原理和使用技巧
|
8月前
|
存储 安全 测试技术
Golang Context 详细原理和使用技巧
Golang Context 详细原理和使用技巧