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 select语句轻松实现高效并发

    一文带你学会Go select语句轻松实现高效并发

    这篇文章主要为大家详细介绍了Golang中select语句的用法,文中的示例代码讲解详细,对我们学习Golang有一定的帮助,需要的可以参考一下
    2023-03-03
  • Go语言通道之无缓冲通道与缓冲通道详解

    Go语言通道之无缓冲通道与缓冲通道详解

    通道是一种特殊的数据结构,可以在协程之间进行传递数据,从而实现协程之间的通信和同步,本文就来和大家讲讲Go语言通道中的无缓冲通道与缓冲通道吧
    2023-06-06
  • Golang使用Gin框架实现HTTP响应格式统一处理

    Golang使用Gin框架实现HTTP响应格式统一处理

    在gin框架中,我们可以定义一个中间件来处理统一的HTTP响应格式,本文主要为大家介绍了具体是怎么定义实现这样的中间件的,感兴趣的小伙伴可以了解一下
    2023-07-07
  • 详解Go语言中init的使用与常见应用场景

    详解Go语言中init的使用与常见应用场景

    Go 中有一个特别的 init() 函数,它主要用于包的初始化,这篇文章将以此为主题介绍 Go 中 init() 函数的使用和常见使用场景,希望对大家有所帮助
    2024-02-02
  • 深入解析Sync.Pool如何提升Go程序性能

    深入解析Sync.Pool如何提升Go程序性能

    在并发编程中,资源的分配和回收是一个很重要的问题。Go 语言的 Sync.Pool 是一个可以帮助我们优化这个问题的工具。本篇文章将会介绍 Sync.Pool 的用法、原理以及如何在项目中正确使用它,希望对大家有所帮助
    2023-05-05
  • VS Code配置Go语言开发环境的详细教程

    VS Code配置Go语言开发环境的详细教程

    这篇文章主要介绍了VS Code配置Go语言开发环境的详细教程,本文通过实例代码图文相结合的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • Go语言遍历目录的三种方法举例

    Go语言遍历目录的三种方法举例

    学习io之后,尤其是文件操作,我们就可以遍历给定的目录了,这篇文章主要给大家介绍了关于Go语言遍历目录的三种方法,分别是ioutil.ReadDir、filepath.Walk以及filepath.Glob,需要的朋友可以参考下
    2023-11-11
  • 详解Go语言中的作用域和变量隐藏

    详解Go语言中的作用域和变量隐藏

    这篇文章主要为大家介绍了Go语言中的作用域和变量隐藏,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,感兴趣的小伙伴可以了解一下
    2022-04-04
  • 使用Go语言编写一个毫秒级生成组件库文档工具

    使用Go语言编写一个毫秒级生成组件库文档工具

    在开发组件库的过程中,文档无疑是不可或缺的一环,在本文中将尝试将Go语言与前端技术巧妙融合,以创建一款能在毫秒级别完成文档生成的工具,需要的可以参考下
    2024-03-03
  • Windows下在CMD下执行Go出现中文乱码的解决方法

    Windows下在CMD下执行Go出现中文乱码的解决方法

    在cmd下运行go程序或者是GOLAND的Terminal下运行go程序会出现中文乱码的情况。本文就详细的介绍下解决方法,具有一定的参考价值,感兴趣的可以了解一下
    2021-12-12

最新评论