Goroutine 是 Go 语言中的轻量级并发执行单元。
它类似于线程,但比线程更轻量、更高效,创建和切换的开销很小。多个 Goroutine 可以在同一个进程中并发执行,共享该进程的地址空间。
创建 Goroutine 非常简单,只需在函数或方法调用前加上 go
关键字即可。例如:
package main
import (
"fmt"
"time"
)
func task(name string) {
for i := 1; i <= 5; i++ {
fmt.Printf("%s: %d\n", name, i)
time.Sleep(time.Millisecond * 500)
}
}
func main() {
go task("Goroutine 1")
go task("Goroutine 2")
time.Sleep(time.Second * 3)
fmt.Println("Main function done")
}
在上述示例中,通过 go task("Goroutine 1")
和 go task("Goroutine 2")
启动了两个 Goroutine 来并发执行 task
函数
Go 语言的并发模型基于三个核心概念:Goroutines(G)、程序计数器(Program Counter,PC)和机器(Machines,M)。这个模型通常被称为 Go 的 GPM 模型。
-
Goroutine(G):这是 Go 语言中并发执行的基本单元。每个 goroutine 代表一个执行的上下文,拥有自己的栈(Stack)和局部变量。goroutine 比传统的线程更轻量级,因为它们由 Go 运行时进行调度和管理,而不是操作系统。
-
程序计数器(PC):每个 goroutine 都有一个程序计数器,它记录了 goroutine 当前执行的指令位置。当 goroutine 被调度执行时,它的 PC 会被加载到 CPU 的寄存器中。
-
机器(Machine,M):机器是 Go 运行时用来执行 goroutine 的系统线程。每个 M 都有一个本地运行队列(Local Run Queue,LRQ),用于存放待执行的 goroutines。M 从 LRQ 中选择 goroutine 来执行,也可以从其他 M 的 LRQ 中窃取(Steal)goroutines 来平衡负载。
-
逻辑处理器(Logical Processor,P):逻辑处理器是 Go 运行时用来管理 M 的抽象概念。每个 P 都有一个全局运行队列(Global Run Queue,GRQ),用于存放待执行的 goroutines。P 负责将 goroutine 分发到 M 上执行,并且协调 M 之间的工作负载平衡。
-
系统线程(System Thread):操作系统级别的线程,每个 M 都对应一个系统线程。
-
工作窃取(Work Stealing):当一个 M 的 LRQ 变空时,它会尝试从其他 M 的 LRQ 中窃取 goroutines 来执行,以实现工作负载的平衡。
-
栈:每个 goroutine 都有自己的栈,用于存储局部变量和调用栈。Go 的栈是动态的,可以按需增长和收缩。
-
调度器:Go 运行时的调度器负责管理 goroutines 的执行。它根据 P 和 M 的状态来调度 goroutines,确保系统的并发性和效率。
-
同步:Go 运行时提供了多种同步原语,如互斥锁(sync.Mutex)、条件变量(sync.Cond)、信号量(sync.WaitGroup)等,用于协调 goroutines 之间的操作。
-
通道(Channel):Go 中的通道用于在 goroutines 之间进行通信和同步。通道可以是带缓冲的或不带缓冲的,支持有界和无界的数据传输。
GPM 模型的设计使得 Go 语言能够有效地处理并发,同时避免了传统多线程编程中的一些常见问题,如死锁和竞态条件。通过这种模型,Go 语言提供了一种简单而强大的并发编程方式。