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

相关文章
|
3月前
|
编译器 Go
Golang底层原理剖析之method
Golang底层原理剖析之method
30 2
|
3月前
|
存储 Go
Golang底层原理剖析之map
Golang底层原理剖析之map
44 1
|
3月前
|
Java Go
Golang底层原理剖析之垃圾回收GC(二)
Golang底层原理剖析之垃圾回收GC(二)
78 0
|
3月前
|
存储 SQL 安全
Golang底层原理剖析之上下文Context
Golang底层原理剖析之上下文Context
96 0
|
6天前
|
存储 人工智能 Go
golang 反射基本原理及用法
golang 反射基本原理及用法
8 0
|
2月前
|
Go
GoLang 使用 goroutine 停止的几种办法
GoLang 使用 goroutine 停止的几种办法
32 2
|
3月前
|
监控 Go 开发者
Golang深入浅出之-Goroutine泄漏检测与避免:pprof与debug包
【5月更文挑战第2天】本文介绍了Go语言并发编程中可能遇到的Goroutine泄漏问题,以及如何使用`pprof`和`debug`包来检测和防止这种泄漏。常见的问题包括忘记关闭channel和无限制创建goroutine。检测方法包括启动pprof服务器以监控Goroutine数量,使用`debug.Stack()`检查堆栈,以及确保每个Goroutine有明确的结束条件。通过这些手段,开发者可以有效管理Goroutine,维持程序性能。
126 7
|
3月前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
405 1
|
3月前
|
Java Go
Golang深入浅出之-Goroutine泄漏检测与避免:pprof与debug包
【5月更文挑战第1天】本文介绍了Go语言中goroutine泄漏的问题及其影响,列举了忘记关闭通道、无限循环和依赖外部条件等常见泄漏原因。通过引入`net/http/pprof`和`runtime/debug`包,可以检测和避免goroutine泄漏。使用pprof的HTTP服务器查看goroutine堆栈,利用`debug`包的`SetGCPercent`和`FreeOSMemory`函数管理内存。实践中,应使用`sync.WaitGroup`、避免无限循环和及时关闭通道来防止泄漏。理解这些工具和策略对维护Go程序的稳定性至关重要。
94 4
|
3月前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
57 2