Go 语言中控制协程数量的常用方法小结

 更新时间:2025年11月14日 09:29:31   作者:极客李华  
本文介绍了Go语言中四种控制协程数量的常用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在 Go 语言中,协程(goroutine)是轻量级的执行单元,虽然开销小,但无限制地创建协程仍然会消耗大量系统资源,甚至导致程序崩溃。因此,合理控制协程数量是编写高效 Go 程序的关键。本文将介绍几种常用的协程数量控制方法,并结合具体案例说明其用法。

一、使用带缓冲的通道控制

带缓冲的通道可以作为一个简易的信号量(Semaphore),通过控制通道的容量来限制同时运行的协程数量。

基本原理:

  • 创建一个指定容量的通道
  • 启动协程前先向通道发送信号(获取令牌)
  • 协程结束后从通道接收信号(释放令牌)
  • 当通道已满时,新的协程需要等待直到有令牌释放

案例代码:

package main

import (
	"fmt"
	"time"
)

func worker(id int, sem chan struct{}) {
	defer func() { <-sem }() // 释放令牌
	fmt.Printf("Worker %d 开始工作\n", id)
	time.Sleep(time.Second) // 模拟工作
	fmt.Printf("Worker %d 完成工作\n", id)
}

func main() {
	const maxGoroutines = 3 // 最大协程数量
	sem := make(chan struct{}, maxGoroutines)
	totalTasks := 10 // 总任务数

	for i := 0; i < totalTasks; i++ {
		sem <- struct{}{} // 获取令牌,若满则等待
		go worker(i, sem)
	}

	// 等待所有令牌被释放(所有协程完成)
	for i := 0; i < cap(sem); i++ {
		sem <- struct{}{}
	}
	fmt.Println("所有任务完成")
}

二、使用 sync.WaitGroup 配合通道

sync.WaitGroup 用于等待一组协程完成,结合通道可以更灵活地控制协程数量。

案例代码:

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d 开始工作\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d 完成工作\n", id)
}

func main() {
	const maxGoroutines = 3
	sem := make(chan struct{}, maxGoroutines)
	var wg sync.WaitGroup
	totalTasks := 10

	for i := 0; i < totalTasks; i++ {
		sem <- struct{}{}
		wg.Add(1)
		go func(id int) {
			defer func() { <-sem }()
			worker(id, &wg)
		}(i)
	}

	wg.Wait() // 等待所有任务完成
	fmt.Println("所有任务完成")
}

三、使用工作池(Worker Pool)模式

工作池模式创建固定数量的工作协程,从任务队列中获取任务执行,适用于任务数量多且可批量处理的场景。

案例代码:

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
	defer wg.Done()
	for job := range jobs {
		fmt.Printf("Worker %d 处理任务 %d\n", id, job)
		time.Sleep(time.Second) // 模拟处理时间
		results <- job * 2      // 模拟处理结果
	}
}

func main() {
	const (
		numWorkers = 3    // 工作协程数量
		numJobs    = 10   // 任务数量
	)

	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)
	var wg sync.WaitGroup

	// 启动工作协程
	wg.Add(numWorkers)
	for w := 1; w <= numWorkers; w++ {
		go worker(w, jobs, results, &wg)
	}

	// 发送任务
	go func() {
		for j := 1; j <= numJobs; j++ {
			jobs <- j
		}
		close(jobs) // 所有任务发送完毕,关闭通道
	}()

	// 等待所有工作协程完成
	go func() {
		wg.Wait()
		close(results) // 所有结果处理完毕,关闭通道
	}()

	// 收集结果
	for result := range results {
		fmt.Printf("收到结果: %d\n", result)
	}

	fmt.Println("所有任务完成")
}

四、使用第三方库

对于复杂场景,可以使用成熟的第三方库,如 golang.org/x/sync/errgroupgithub.com/panjf2000/ants(高性能协程池)。

使用 errgroup 的案例:

package main

import (
	"context"
	"fmt"
	"golang.org/x/sync/errgroup"
	"time"
)

func worker(id int) error {
	fmt.Printf("Worker %d 开始工作\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d 完成工作\n", id)
	return nil
}

func main() {
	const maxGoroutines = 3
	g, ctx := errgroup.WithContext(context.Background())
	g.SetLimit(maxGoroutines) // 设置最大并发数
	totalTasks := 10

	for i := 0; i < totalTasks; i++ {
		id := i
		g.Go(func() error {
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				return worker(id)
			}
		})
	}

	if err := g.Wait(); err != nil {
		fmt.Printf("发生错误: %v\n", err)
	} else {
		fmt.Println("所有任务完成")
	}
}

到此这篇关于Go 语言中控制协程数量的常用方法小结的文章就介绍到这了,更多相关Go 控制协程数量内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用go求幂的几种方法小结

    使用go求幂的几种方法小结

    这篇文章主要介绍了使用go求幂的几种方法小结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Qt6.5 grpc组件使用 + golang grpc server示例详解

    Qt6.5 grpc组件使用 + golang grpc server

    这篇文章主要介绍了Qt6.5 grpc组件使用+golang grpc server示例,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • 如何判断Golang接口是否实现的操作

    如何判断Golang接口是否实现的操作

    这篇文章主要介绍了如何判断Golang接口是否实现的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go tablewriter库提升命令行输出专业度实例详解

    Go tablewriter库提升命令行输出专业度实例详解

    命令行工具大家都用过,如果是运维人员可能会编写命令行工具来完成各种任务,命令行输出的美观和易读性往往容易被忽视,很烂的输出会让人感觉不专业,本文将介绍Go语言中牛逼的实战工具tablewriter库,使你在命令行输出中展现出专业的一面
    2023-11-11
  • Go语言使用Redis和Etcd实现高性能分布式锁

    Go语言使用Redis和Etcd实现高性能分布式锁

    这篇文章主要为大家介绍了Go语言使用Redis实现高性能分布式锁示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • golang调用shell命令(实时输出,终止)

    golang调用shell命令(实时输出,终止)

    本文主要介绍了golang调用shell命令(实时输出,终止),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Go结构体SliceHeader及StringHeader作用详解

    Go结构体SliceHeader及StringHeader作用详解

    这篇文章主要为大家介绍了Go结构体SliceHeader及StringHeader作用的功能及面试官爱问的实际意义详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • golang高并发之本地缓存详解

    golang高并发之本地缓存详解

    这篇文章主要为大家详细介绍了golang高并发中本地缓存的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-10-10
  • Go编译原理之函数内联

    Go编译原理之函数内联

    这篇文章主要为大家介绍了Go编译原理之函数内联示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go panic的三种产生方式细节探究

    Go panic的三种产生方式细节探究

    这篇文章主要介绍了Go panic的三种产生方式细节探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12

最新评论