golang,go,博客,开源,编程

go协程初识

Published on with 0 views and 0 comments

在 Go 语言中,协程(goroutine)是实现并发的核心机制。它是 Go 语言对线程的轻量级抽象,可以在程序中并发地执行多个任务。通过 goroutine,Go 可以高效地进行并发执行,并且相比传统操作系统线程更加轻量。

Go 协程(goroutine)的结构

1. Goroutine 是用户级线程

  • Goroutine 是 Go 语言的并发执行单位,类似于线程,但是比线程要轻量级得多。
  • 在 Go 中,使用 go 关键字来启动一个新的 Goroutine。例如:
    go func() {
        // 执行的任务
    }()
    
  • 这将创建一个新的 Goroutine 来执行指定的函数,Go 运行时调度器会自动将这个任务分配到合适的处理器(P)上。

2. 每个 Goroutine 都有一个独立的栈

  • 每个 Goroutine 在启动时会分配一个小的初始栈空间(大约 2KB)。
  • 由于 Go 的栈是动态增长的,当 Goroutine 执行的任务需要更多的栈空间时,Go 会自动扩展栈。
  • 这种设计使得创建大量的 Goroutine 比使用操作系统线程更加高效。

3. Goroutine 与 M、P 的关系(GMP 模型)

  • G:代表一个 Goroutine。每个 Goroutine 都有自己的栈、程序计数器(PC)等上下文信息,负责执行某个任务。
  • M:代表操作系统线程(Machine)。M 是 Go 运行时管理的线程,负责执行 Goroutine 中的代码。
  • P:代表逻辑处理器(Processor)。每个 P 管理着一组 Goroutine 和它们对应的调度任务,P 负责将 Goroutine 分配给 M 来执行。

在 Go 语言的 GMP 模型中,**M(操作系统线程)会执行P(处理器)**调度的任务,P 会从其队列中取出 Goroutine(G)来执行。Go 的调度器负责在多个 P 和 M 之间调度 Goroutine 的执行。

4. Goroutine 的调度机制

  • Go 运行时有一个协作式调度器。调度器负责将 Goroutine 按照一定规则分配到不同的 P 上,并通过 M(操作系统线程)来执行它们。
  • 每个 Goroutine 会被调度到一个 P 上执行,而每个 P 会绑定到一个 M(操作系统线程)。多个 Goroutine 可以共享同一个 P,也可以通过工作窃取机制在不同的 P 之间转移。
  • Go 语言的调度器支持多个 Goroutine 在多个 CPU 核心上并行执行,能够有效利用多核 CPU。

5. Goroutine 的生命周期

  • 新建:当创建一个 Goroutine 时,Go 运行时会分配一个小的栈并初始化相关的调度信息。
  • 就绪:创建后的 Goroutine 会进入就绪队列,等待被调度器分配到一个 P 上执行。
  • 执行:调度器会将 Goroutine 分配给 M 来执行任务。
  • 阻塞:如果一个 Goroutine 执行时被阻塞(如等待 I/O 操作、锁等),它会被挂起,Go 调度器会重新调度其他的 Goroutine。
  • 完成:执行完毕的 Goroutine 会被销毁,释放资源。

6. Goroutine 与 OS 线程的区别

  • 轻量级:Goroutine 是比操作系统线程更加轻量级的执行单元。它的创建和销毁都非常快速,占用的内存也远小于操作系统线程。
  • 并发调度:Go 的运行时调度器负责管理所有的 Goroutine,它通过调度算法(如协作式调度)高效地在多个处理器(P)和操作系统线程(M)之间分配 Goroutine 的执行。

7. Goroutine 的内存模型

  • 每个 Goroutine 都有自己的栈,栈大小通常在 2KB 左右,并且是动态可扩展的。Go 在运行时会根据需要自动增加栈的大小。
  • Goroutine 可以通过 channels 来进行通信和同步,确保并发操作之间的数据一致性。

Goroutine 和 OS 线程对比

特性Goroutine操作系统线程
创建开销较小(内存占用少)较大(需要操作系统的资源)
切换开销较低(由 Go 调度器管理)较高(由操作系统管理)
内存占用2KB 起(动态扩展)相对较高
并发性高并发,能够创建成千上万个 Goroutine通常有限制,受到系统线程数的限制
管理Go 运行时调度器自动管理由操作系统调度管理
调度方式用户级调度(Go 调度器负责调度)内核级调度(操作系统调度)

启动 Goroutine 示例

下面是一个简单的 Go 程序,演示如何启动多个 Goroutine。

package main

import (
	"fmt"
	"time"
)

// 执行的任务
func task(id int) {
	fmt.Printf("Task %d is running\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Task %d is completed\n", id)
}

func main() {
	// 启动 5 个 Goroutine
	for i := 0; i < 5; i++ {
		go task(i) // 使用 go 关键字启动一个新的 Goroutine
	}

	// 等待 Goroutine 执行完成
	time.Sleep(2 * time.Second) // 等待足够的时间让 Goroutine 完成执行
}

输出示例:

Task 0 is running
Task 1 is running
Task 2 is running
Task 3 is running
Task 4 is running
Task 0 is completed
Task 1 is completed
Task 2 is completed
Task 3 is completed
Task 4 is completed

关键点总结:

  1. 轻量级:Goroutine 是比线程更轻量的并发执行单元,启动和销毁的开销小。
  2. 动态栈:每个 Goroutine 拥有自己的栈,并且栈大小是动态增长的,默认起始栈大小为 2KB。
  3. 并发调度:Go 运行时负责调度多个 Goroutine,在多核 CPU 上高效运行。
  4. 调度器:Go 的调度器根据 GMP 模型(Goroutine, Machine, Processor)管理 Goroutine 的执行,保证高效的并发执行。
  5. 同步与通信:Goroutine 之间可以通过 channels 或其他同步机制进行通信,保证数据的一致性。

通过 Goroutine 和 Go 调度器,Go 语言能够高效地支持大规模的并发操作,特别适用于 Web 服务、分布式系统等高并发场景。


标题:go协程初识
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/01/06/1736152935639.html
联系:scotttu@163.com