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 之间的通信和同步。

相关文章
|
9月前
|
Java Go
Golang底层原理剖析之垃圾回收GC(二)
Golang底层原理剖析之垃圾回收GC(二)
136 0
|
5月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
116 4
|
3月前
|
存储 安全 测试技术
GoLang协程Goroutiney原理与GMP模型详解
本文详细介绍了Go语言中的Goroutine及其背后的GMP模型。Goroutine是Go语言中的一种轻量级线程,由Go运行时管理,支持高效的并发编程。文章讲解了Goroutine的创建、调度、上下文切换和栈管理等核心机制,并通过示例代码展示了如何使用Goroutine。GMP模型(Goroutine、Processor、Machine)是Go运行时调度Goroutine的基础,通过合理的调度策略,实现了高并发和高性能的程序执行。
252 29
|
3月前
|
负载均衡 算法 Go
GoLang协程Goroutiney原理与GMP模型详解
【11月更文挑战第4天】Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理,创建和销毁开销小,适合高并发场景。其调度采用非抢占式和协作式多任务处理结合的方式。GMP 模型包括 G(Goroutine)、M(系统线程)和 P(逻辑处理器),通过工作窃取算法实现负载均衡,确保高效利用系统资源。
|
5月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
145 4
Golang语言goroutine协程篇
|
6月前
|
存储 关系型数据库 Go
SOLID原理:用Golang的例子来解释
SOLID原理:用Golang的例子来解释
|
6月前
|
算法 NoSQL 关系型数据库
熔断原理与实现Golang版
熔断原理与实现Golang版
|
6月前
|
NoSQL Unix 编译器
Golang协程goroutine的调度与状态变迁分析
文章深入分析了Golang中goroutine的调度和状态变迁,包括Grunnable、Gwaiting、Grunning和Gsyscall等状态,以及它们之间的转换条件和原理,帮助理解Go调度器的内部机制。
80 0
|
6月前
|
存储 人工智能 Go
golang 反射基本原理及用法
golang 反射基本原理及用法
48 0
|
8月前
|
Go
GoLang 使用 goroutine 停止的几种办法
GoLang 使用 goroutine 停止的几种办法
77 2