Go中Goroutine简单教程

Goroutine 是 Go 编程语言中的轻量级、独立执行的并发控制线程。Goroutines 是 Go 中并发和并行编程的基本构建块。它们允许您并发执行函数,从而可以轻松地以可读且可管理的方式编写并发程序。

使用 Goroutine 可以实现什么目的?
并发性:它们支持任务的并发执行,从而更容易编写可利用多核处理器的高效且响应迅速的程序。

简单性: Goroutine 通过提供易于使用、轻量级的并发模型,使并发编程变得更容易。它们比传统的线程或进程更容易使用。

效率: Goroutines 在内存和资源方面都是轻量级的,因此您可以创建许多 Goroutine,而不会显着影响程序的性能。

异步操作: Goroutine 适合处理 I/O 密集型任务和异步任务,而不会阻塞主线程,从而使您的程序保持响应能力。

何时使用 Goroutine?
当你想要实现任务的并发执行时,Go 中就会使用 Goroutines。它们在您具有可以独立且同时执行的任务的情况下特别有用,例如:

同时处理多个网络连接。

  1. • 执行数据并行处理,例如在map-reduce 任务中。
  2. • 运行后台任务,例如监视、日志记录或定期清理。
  3. • 无阻塞地处理并发I/O 操作。
  4. • 您想要实现并发并利用多核处理器的任何情况

代码:

package main 

import
 "fmt" 
 
"sync"
 ) 

func  main () { 
fmt.Println(
"Start"

 var wgsync.WaitGroup 

 
// 创建两个 goroutine func1 和 func2
 wg.Add( 2 ) 
 go func1(&wg) 
 go func2(&wg) 

 
// 等待两个 goroutine 完成
wg.Wait() 

fmt.Println(
"End"


func  func1 (wg *sync.WaitGroup) { 
 defer wg.Done() 
fmt.Println(
"func 1"


func  func2 (wg *sync.WaitGroup) { 
 defer wg.Done() 
fmt.Println(
"func 2"


// 开始
// func 1 
// func 2 
// 结束

  • 当使用 go 关键字创建 goroutine 时,它​​会被添加到要调度的 goroutine 队列中。
  • Go 调度程序从池中选择一个空闲的逻辑处理器 (P),并分配下一个 goroutine 在该 P 上运行
  • P 的关联线程 (M) 执行该 goroutine 的代码。
  • 当 goroutine 被抢占时(例如,由于系统调用或阻塞操作),调度程序可以切换到同一 P 上的另一个 goroutine。
  • 如果 goroutine 自愿让出控制权(例如,使用 runtime.Gosched()),调度程序可以切换到另一个goroutine。
  • Go 调度程序管理 Goroutine 的协调,当 Goroutine 完成时,它可能会被终止。

Goroutine 生命周期
Created:当您使用 go 关键字创建 Goroutine 时,它​​会转换到“已创建”状态。在这种状态下,Goroutine 已准备好运行,但尚未开始执行。Go 运行时调度程序最终会在 CPU 时间可用时启动它。

Running运行:一旦调度程序开始执行 Goroutine,它就进入“运行”状态。在这种状态下,Goroutine 的代码正在处理器(CPU 核心)上主动执行。Goroutine 可以将控制权交给调度程序,从而允许其他 Goroutine 运行。

Blocked阻塞: Goroutine 可能会因各种原因转换为“阻塞”状态,例如: 当通道为空时等待通道上的输入(通道 <- 数据)。获取当前由另一个 Goroutine 持有的锁或互斥体。进行阻塞的系统调用(例如,从文件中读取或等待网络数据)。等待满足特定事件或条件。在“阻塞”状态下,Goroutine 暂时暂停并放弃其 CPU 时间,直到阻塞条件解决。

退出:当 Goroutine 完成执行时,就会出现“退出”状态。
当 Goroutine 到达其函数末尾、返回值或遇到错误时,可能会发生这种情况。在“退出”状态下,Goroutine 终止,其资源被释放。它不再参与程序的并发执行。

M:N调度器
在 Go 中,M:N 调度程序是运行时系统的关键组件,用于管理 goroutine 及其在多个操作系统线程上的执行。
M:N 调度程序模型旨在有效管理少量操作系统线程上的大量 goroutine,提供并发性和并行性,同时最大限度地减少线程创建和管理的开销。

以下是 Go 中 M:N 调度程序的工作原理:

  • Goroutines (M): Goroutines 是 Go 中的轻量级执行线程。它们由 Go 运行时管理,并且可以轻松生成。Goroutine 被复用到较少数量的操作系统线程上。
  • 操作系统线程 (N): Go 运行时使用较少数量的操作系统线程,通常等于可用 CPU 核心的数量。这些操作系统线程负责执行 goroutine。操作系统线程由 Go 运行时管理,通常被称为“M”,因为它们与它们运行的​​ goroutine 密切相关。

单内核线程如何复用goroutine?
在Go中,其并发模型的一个关键特性是单个内核线程支持多个goroutine的能力。与许多其他编程语言中的传统线程相比,这种方法是 Go 的 goroutine 如此高效和轻量级的部分原因。

内核线程:在典型的操作系统中,内核线程是操作系统调度程序管理的执行单元。每个内核线程都与特定的操作系统级堆栈和其他资源相关联。

Goroutines: Go 中实现的 Goroutines 是轻量级的执行线程。它们不是一对一映射到内核线程的。相反,多个 goroutine 可以在单个内核线程上运行。这是通过 Go 运行时的调度程序实现的,它将 goroutine 多路复用到较少数量的操作系统线程上。

并发性:当多个 goroutine 在单个内核线程上运行时,它们轮流执行。Go 调度程序管理 goroutine 之间的切换,确保它们获得公平的 CPU 时间份额。这种调度是协作发生的,这意味着 goroutine 自愿让出控制权,例如在 I/O 操作期间或当它们调用 runtime.Gosched() 时。

效率:这种设计允许 Go 创建和管理大量的 goroutine,而无需创建和管理同等数量的操作系统线程的开销。在许多其他编程语言中,创建和管理线程在内存和 CPU 资源方面都相对昂贵。

系统调用:当 goroutine 进行通常会阻塞内核线程的系统调用时,Go 运行时可以将 goroutine 迁移到另一个可用的内核线程。这种迁移确保系统调用不会阻塞整个操作系统线程并允许其他 goroutine 继续运行。

在单个内核线程上支持多个 goroutine 的能力是 Go 并发模型的基本特征。它允许 Go 高效处理大量并发任务,同时最大限度地减少线程创建和管理的开销。这是 Go 特别适合并发和并行编程的原因之一。