Go协程使用

52 阅读3分钟

goroutine使用

goroutine是Go语言从语言层面提供的支持,使用更少的栈内存,是一种比线程更轻量级的并发实现.因此go相比较其他语言可以表现出更好的并发性能,能够在程序中高效地执行多个任务。

开启协程

通过go关键字

func sayHello() {
	fmt.Println("Hello from Goroutine!")
}
func main() {
	go sayHello() // 启动一个新的 Goroutine
	time.Sleep(time.Second) // 等待 Goroutine 执行
}

Goroutine 是异步执行的,主 Goroutine(main 函数)不会等待其他 Goroutine 自动完成,因此像下面这种情况,循环走的比协程开启得快,打印的都是11111......

for i := 1; i <= 10; i++ {
	go func(){
		fmt.Println(i)
	}()
}

互斥锁

sync.Mutex提供了两种基本方法:

  • Lock():加锁,当锁已经被其他 Goroutine 持有时,会阻塞当前 Goroutine。
  • Unlock():解锁,释放锁资源,允许其他 Goroutine 获得锁 如果某些 Goroutine 只需要读取数据,可以使用读写锁 sync.RWMutex。它允许多个 Goroutine 同时读取,但写操作仍是独占的。

sync.WaitGroup

用于等待一组 Goroutine 完成执行,允许主 Goroutine等待多个子 Goroutine 执行完成后再继续执行。

基本方法

  1. Add(delta int) :增加或减少等待计数。

    • 参数 delta:可以为正(增加计数)或负(减少计数)。
  2. Done() :减少等待计数,等价于 Add(-1)

  3. Wait() :阻塞调用它的 Goroutine,直到等待计数变为 0。

使用流程

  1. 创建一个 sync.WaitGroup 实例。
  2. 在启动每个 Goroutine 前调用 Add(1) 增加计数。
  3. 在每个 Goroutine 完成任务后调用 Done(),减少计数。
  4. 主 Goroutine 调用 Wait(),等待计数变为 0,继续执行。Wait() 会阻塞调用它的 Goroutine,通常用于主 Goroutine 等待子 Goroutine。
func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Goroutine 完成时减少计数

	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second) // 模拟任务耗时
	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)
	}
	wg.Wait() // 等待所有 Goroutine 完成
}

可能存在问题

  1. 如果调用了 Add(1) 却忘记调用 Done()Wait() 会永久阻塞。因此要使用 defer wg.Done(),确保在 Goroutine 中任务完成后减少计数。
  2. 需要使用defer ,避免忘记调用 Done()

Goroutine 调度

Go 的调度器基于 GMP 模型,实现了高效的协程调度机制,runtime层面的实现,是完全由 Go 语言本身实现的一套调度系统——go scheduler。调度器会采用本地队列、任务窃取和抢占式调度等优化机制。 其中,GMP 模型由以下三个主要部分组成:

  1. G(Goroutine)
    • 表示 Goroutine,Go 中的轻量级线程。
    • 每个 Goroutine 都包含需要执行的函数、堆栈信息等。
    • 调度器会把 G 分配到 P 中排队等待执行。
  2. M(Machine)
    • 表示操作系统的线程。
    • 每个 M 绑定一个内核线程,负责实际执行代码。
    • M 通过绑定 P 来获取 Goroutine。
  3. P(Processor)
    • 表示逻辑处理器,负责调度 Goroutine。
    • 每个 P 维护一个本地队列,存储需要运行的 Goroutine。
    • P 的数量由环境变量 GOMAXPROCS 控制。