Golang中是否可以无限开辟协程以及如何控制协程的数量?

news/2025/2/25 23:06:30

文章目录

  • 1. Golang中是否可以无限开辟协程?
  • 2. 不控制goroutine数量引发的问题
  • 3. 如何控制goroutine的数量?⭐️
    • 3.1 只用有buffer的channel
    • 3.2 channel与sync同步组合方式
    • 3.3 利用无缓冲channel与任务发送/执行分离方式

1. Golang中是否可以无限开辟协程?

首先我们在linux操作系统上运行以下这段程序,看会发生什么?

package main
  
import (
        "fmt"
        "math"
        "runtime"
)


// 测试是否可以无限go
func main(){
        // 模式业务需要开辟的数量
        task_cnt := math.MaxInt64
        
        for i := 0 ; i< task_cnt;i++ {
             go func (num int){
                 // 完成一些业务
                 fmt.Println("go func ",i," goroutine count=",runtime.NumGoroutine())
             }(i)
        }
}

程序运行在中途主进程直接被操作系统杀死,如下图所示:
请添加图片描述

2. 不控制goroutine数量引发的问题

我们知道goroutine具备轻量高效GPM调度的特点,如果无限开辟goroutine,短时间内会占用大量的占用操作系统的资源(文件描述符、CPU、内存等):

  • CPU浮动上涨;
  • 内存占用持续身高;
  • 主进程被操作系统杀死;

这些资源实际上是用户态程序共享的资源,所以大批的goroutine最终引发灾难不仅仅是自身,还会关联其他运行的程序。

3. 如何控制goroutine的数量?⭐️

3.1 只用有buffer的channel

例如使用一个有缓冲的channel。当channel满了的时候,其会发生阻塞,避免一直不断的开辟goroutine。其设计逻辑如下:
请添加图片描述
完整代码如下:

package main
  
import (
        "fmt"
        "math"
        "runtime"
)

func MyWork(c chan bool,i int){
        fmt.Println("go func ",i," goroutine count=",runtime.NumGoroutine())
        <- c
}

// 测试是否可以无限go
func main(){
        // 模式业务需要开辟的数量
        task_cnt := math.MaxInt64
        // 创建一个带缓冲的channel
        myChan := make(chan bool,3)

        // 循环创建业务
        for i := 0 ; i< task_cnt;i++ {
                myChan <- true
                go  MyWork(myChan,i)
        }
}

按照上面的方式使得能够一直运行。其实实际上,执行的只有3个(还有一个main goroutine)。上面代码的本质就是在myChan <- true处会阻塞,直到之前三个中有一个完成了任务,阻塞接触,才开辟一个新的goroutine。
请添加图片描述

3.2 channel与sync同步组合方式

  • 如果我们只使用sync的WaitGroup会怎么样?
    package main
      
    import (
            "fmt"
            "math"
            "runtime"
            "sync"
    )
    
    // 创建一个全局的wait_group{}
    var wg = sync.WaitGroup{}
    
    func MyWork(i int){
            fmt.Println("go func ",i," goroutine count=",runtime.NumGoroutine())
            wg.Done()
    }
    
    // 测试是否可以无限go
    func main(){
            // 模式业务需要开辟的数量
            task_cnt := math.MaxInt64
    
            // 循环创建业务
            for i := 0 ; i< task_cnt;i++ {
                    wg.Add(1)
                    go  MyWork(i)
            }
            // 阻塞等待
            wg.Wait()
    }
    
    
    结果是仍然无法大量开辟,主线程会被操作系统杀死。
    请添加图片描述
  • channel与sync同步组合方式
    package main
    
    import (
            "fmt"
            "math"
            "runtime"
            "sync"
    )
    
    // 创建一个sync.WaitGroup{}变量
    var wg = sync.WaitGroup{}
    
    func MyWork(c chan bool,i int){
            fmt.Println("go func ",i," goroutine count=",runtime.NumGoroutine())
            wg.Done()
            <- c
    }
    
    // 测试是否可以无限go
    func main(){
            // 模式业务需要开辟的数量
            task_cnt := math.MaxInt64
            // 创建一个带缓冲的channel
            myChan := make(chan bool,3)
    
            // 循环创建业务
            for i := 0 ; i< task_cnt;i++ {
                    wg.Add(1)
                    myChan <- true
                    go  MyWork(myChan,i)
            }
            wg.Wait()
    }
    

3.3 利用无缓冲channel与任务发送/执行分离方式

代码逻辑:

package main

import (
	"fmt"
	"math"
	"runtime"
	"sync"
)

// 定义一个WaitGroup类型的变量,保证所有的任务都能执行完毕
var wg = sync.WaitGroup{}

// 任务执行函数
func MyWork(c chan int){
	// 表示业务执行完毕
	defer wg.Done()
	for t := range c {
		// 模拟业务处理逻辑
		fmt.Println(t)
		
	}
}

// 发送业务的函数
func SendTask(c chan int,task int){
	// 保证所有的任务都能执行完毕
	wg.Add(1)
	c <- task
}

func main(){
	// 创建一个无缓冲的通道
	myChan := make(chan int)
	// 开辟固定数量的协程
	for i := 0; i < 3 ; i++ {
		go MyWork(myChan) // 他们都会各自内部阻塞,等待任务发送过来
	}
	// 最大任务数量
	task_cnt := math.MaxInt64
	// 开始发送任务
	for i := 0 ; i < task_cnt ; i++ {
		SendTask(myChan,i)
	}
	// 等待
	wg.Wait()
}

整体架构如下
请添加图片描述
这里实际上是将任务的发送和执行做了业务上的分离。使得输入SendTask的频率可设置、执行Goroutine的数量也可设置。也就是既控制输入(生产),又控制输出(消费)。使得可控更加灵活。这也是很多Go框架的Worker工作池的最初设计思想理念。


http://www.niftyadmin.cn/n/233606.html

相关文章

ECShop开源商城与COS互通:降低本地存储负载、提升访问体验

ECShop简介 ECShop是一款开源电子商务平台&#xff0c;具有简单易用、安全稳定、模块化设计等特点。它提供了完整的电子商务解决方案&#xff0c;包括商品管理、订单管理、支付管理、配送管理、会员管理、促销管理、数据统计等功能。ECShop支持多语言、多货币、多种支付方式和配…

音频相关知识

目录 声音的本质 横波与纵波 为什么固体中既能传输横波&#xff0c;又能传输纵波&#xff0c;液体气体中只能传输纵波 声波 超声波与次声波 声音的三要素 音调 响度 音色 噪声 媒体音频 声道 分类 麦克风工作原理 模数转换 扬声器的使用原理 音频压缩类别 音…

第04讲:实战掌握 Byte Buddy,体验代码生成的顺畅

为什么需要运行时代码生成 我们知道&#xff0c;Java 是一种强类型的编程语言&#xff0c;即要求所有变量和对象都有一个确定的类型&#xff0c;如果在赋值操作中出现类型不兼容的情况&#xff0c;就会抛出异常。强类型检查在大多数情况下是可行的&#xff0c;然而在某些特殊场…

【JS运算】分组求和/平均值(reduce函数)

对于数组求和的问题&#xff0c;使用reduce函数能够最快的解决 如果你还不会reduce函数&#xff0c;可以看这一篇&#xff1a; reduce函数的使用 思路 reduce函数对相同group的值进行迭代求和 将分组的总和除以组里的个数得到平均值&#xff0c;然后存储起来 Sum函数&#x…

opencv:介绍 SIFT(尺度不变特征变换)及其使用(一)

在本章中 我们将了解 SIFT 算法的概念 我们将学习如何找到 SIFT 关键点和描述符。 理论 在过去的几章中,我们了解了一些角点检测器,如 Harris 等。它们具有旋转不变性,这意味着即使图像旋转,我们也可以找到相同的角点。这是显而易见的,因为旋转后的图像中的角点仍然是角点…

激活函数高频面试题集合

激活函数激活函数的作用是什么&#xff1f;常用的激活函数Relu引入Relu的原因Relu顺序relu在零点可导吗&#xff0c;不可导如何进行反向传播&#xff1f;Geluleaky relu优点缺点softmaxsigmoid缺陷tanh缺点如何选择激活函数Bert、GPT、GPT2中用的激活函数是什么&#xff1f;为什…

Charles安装及使用教程

一. 简介及安装 一、charles的使用 1.1 charles的说明 Charles其实是一款代理服务器&#xff0c;通过过将自己设置成系统&#xff08;电脑或者浏览器&#xff09;的网络访问代理服务器&#xff0c;然后截取请求和请求结果达到分析抓包的目的。该软件是用Java写的&#xff0…

nginx优化及配置

nginx隐藏版本号 查看方法 浏览器F12 看network头部看server curl -i 192.168.232.7 获取头部&#xff08;查版本号&#xff09; 配置文件改 添加server_tokens off 改源码 cd /src/core vim nginx.h 修改 修改的IIS为window常用的软件服务 重新编译安装 cd nginx_1.2…