Golang 协程(Goroutine)

简介

Golang(Go 语言)以其强大的并发模型而著称,而协程(Goroutine)是 Go 并发编程的核心特性之一。Goroutine 是一种轻量级线程,由 Go 运行时管理,能够高效地执行并发任务,相比传统线程更占用更少的资源。

本文将深入探讨 Goroutine 的基础概念、使用方法、常见实践以及最佳实践,帮助开发者更好地理解和应用 Go 语言的并发编程能力。


目录

  1. 什么是 Goroutine?
  2. Goroutine 的基本使用
  3. Goroutine 的调度机制
  4. Goroutine 与通道(Channel)通信
  5. Goroutine 的常见实践
  6. Goroutine 的最佳实践
  7. 小结
  8. 参考资料

1. 什么是 Goroutine?

Goroutine 是 Go 语言提供的轻量级并发单元。它类似于线程(Thread),但更加高效。Go 运行时会根据需要创建和管理 Goroutine,而不会像操作系统线程那样占用大量资源。

Goroutine 的特点:

  • 轻量级:一个 Goroutine 仅占用数 KB 内存,而一个线程通常占用数 MB。
  • 用户态调度:Go 运行时自己管理 Goroutine 的调度,而非依赖操作系统的线程调度。
  • 动态扩展:Go 运行时会根据 CPU 核心数合理地调度 Goroutine,从而提高并发执行效率。

2. Goroutine 的基本使用

在 Go 语言中,使用 go 关键字即可启动一个新的 Goroutine。例如:

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	fmt.Println("Hello from Goroutine!")
}

func main() {
	go sayHello() // 启动一个 Goroutine 执行 sayHello()

	fmt.Println("Main function execution")

	time.Sleep(time.Second) // 确保 Goroutine 有时间执行
}

代码解析:

  1. go sayHello() 启动了一个新的 Goroutine 运行 sayHello 函数。
  2. main 函数继续执行,不会等待 sayHello 运行完毕。
  3. time.Sleep(time.Second)main 程序暂停 1 秒,确保 sayHello 运行完成(否则 main 结束后程序直接退出,Goroutine 可能还未执行完)。

⚠️ 注意: main 函数是主 Goroutine,所有 Goroutine 必须在 main 结束前执行,否则会被直接终止。


3. Goroutine 的调度机制

Go 运行时包含一个高效的 Goroutine 调度器,负责在多个 操作系统线程(M) 上调度 Goroutine(G) 的执行。Go 运行时使用 GPM 模型 进行调度:

  • G(Goroutine):表示 Go 协程。
  • M(Thread):表示操作系统线程。
  • P(Processor):表示 Goroutine 运行所需的 CPU 处理器。

Goroutine 的调度策略:

  • 抢占式调度:避免 Goroutine 长时间占用 CPU。
  • 多 P 支持:运行时默认使用 GOMAXPROCS 设定的 CPU 核心数(默认等于 CPU 核心数)。
  • 自动调度:Go 运行时自动决定 Goroutine 何时执行,无需手动管理。

可以通过 runtime.GOMAXPROCS(n) 设置并发执行的 CPU 核心数。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("CPU 核心数:", runtime.NumCPU())      // 获取 CPU 核心数
	fmt.Println("默认 GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 获取当前 GOMAXPROCS 值
	runtime.GOMAXPROCS(2)                             // 设置为 2 个核心
}

4. Goroutine 与通道(Channel)通信

在 Go 语言中,协程之间的通信通常使用 通道(Channel) 进行数据交换。Channel 提供了一种安全的方式在 Goroutine 之间共享数据。

Channel 的基本使用

package main

import "fmt"

func worker(ch chan string) {
	ch <- "Goroutine Message"
}

func main() {
	ch := make(chan string) // 创建一个无缓冲通道
	go worker(ch)           // 启动 Goroutine

	msg := <-ch // 从通道接收数据
	fmt.Println(msg)
}

通道的特点:

  • make(chan T) 创建一个通道(无缓冲)。
  • <- 操作符用于发送或接收数据(阻塞)。
  • 发送方 ch <- "data" 发送数据。
  • 接收方 msg := <-ch 接收数据。

5. Goroutine 的常见实践

1. 使用 sync.WaitGroup 等待所有 Goroutine 完成

package main

import (
	"fmt"
	"sync"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 标记 Goroutine 完成
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1)           // 增加计数
		go worker(i, &wg)   // 启动 Goroutine
	}

	wg.Wait() // 等待所有 Goroutine 完成
	fmt.Println("All workers done")
}

2. 使用 sync.Mutex 进行 Goroutine 资源同步

package main

import (
	"fmt"
	"sync"
)

var counter int
var mu sync.Mutex

func increment() {
	mu.Lock()
	counter++
	mu.Unlock()
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}

	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

6. Goroutine 的最佳实践

  1. 避免过度创建 Goroutine
    • 虽然 Goroutine 轻量,但仍然消耗内存,过多可能导致性能问题。
  2. 优先使用 Channel 而非共享内存
    • Go 提倡 “不要通过共享内存来通信,而是通过通信来共享内存”
  3. 使用 sync.WaitGroup 进行 Goroutine 同步
    • 确保所有 Goroutine 在 main 退出前执行完毕。
  4. 使用 context 控制 Goroutine 生命周期
    • context.WithCancelcontext.WithTimeout 可用于超时控制 Goroutine。

示例:

package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("Worker stopped")
			return
		default:
			fmt.Println("Worker running")
			time.Sleep(500 * time.Millisecond)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	go worker(ctx)

	time.Sleep(2 * time.Second)
	cancel() // 取消 Goroutine
	time.Sleep(1 * time.Second)
}

7. 小结

  • Goroutine 是 Go 语言的核心并发特性,使用 go 关键字创建。
  • Go 运行时使用 GPM 调度模型 高效管理 Goroutine。
  • Channel 是 Goroutine 之间安全的通信方式。
  • sync.WaitGroupsync.Mutexcontext 可用于协程同步和控制。

8. 参考资料

posted @   hyzz123  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示