干货 | Go开发中,如何有效控制Goroutine的并发数量

gRPC与brpc对比:微服务架构下RPC框架选型与性能优化指南 远程过程调用(RPC)是分布式系统与微服务架构中实现服务间通信的核心技术,其核心原理在于封装网络通信细节,使开发者能够像调用本地函数一样调用远程服务。RPC框架通过序列化、服务发现、负载均衡等机制,解决了分布式环境下的服务解耦与高效协作问题,其技术价值在于提升开发效率、保障系统可靠性与可扩展性。在微服务、云原生等应用场景中,RPC已成为构建高性能、松耦合分布式系统的基石。本文聚焦于两大主流高性能RPC框架:基于HTTP/2和Protocol Buffers的云原生标准gRPC,以及百度开源、专为C++极致性 阅读详情

作者简介

 

洋洋,携程高级安全研发工程师,擅长Python、Golang开发,负责安全工具研发。

相信大家在学习Go的过程中,都会看到类似这样一句话:"与传统的系统级线程和进程相比,协程的最大优势在于其‘轻量级’,可以轻松创建上百万个而不会导致系统资源衰竭"。那是不是意味着我们在开发过程中,可以随心所欲的调用协程,而不关心它的数量呢?

答案当然是否定的。我们在开发过程中,如果不对Goroutine加以控制而进行滥用的话,可能会导致服务程序整体崩溃。

这里我先模拟一下协程数量太多的危害:

func main() {
  number := math.MaxInt64
  for i := 0; i < number; i++ {
    go func(i int) {
      // 做一些业务逻辑处理
      fmt.Printf("go func: %d\n", i)
      time.Sleep(time.Second)
    }(i)
  }
}

如果number是用户输入的一个参数,没有做限制。有些开发人员会全部丢进去进行循环,认为全部都并发使用Goroutine去做一件事情,效率比较高。但这样的话,噩梦般的事情就开始了,服务器系统资源利用率不断上涨,到最后程序自动killed。

通过执行top命令查看到该程序占用的CPU、内存较高。

为了避免上图这种情况,下面会简单的介绍一下Goroutine以及在我们日常开发中如何控制Goroutine的数量。

一、基本介绍

工欲善其事必先利其器。先简单的介绍一下Goroutine,Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。

为了更好理解Goroutine,先讲一下进程、线程和协程的概念。

进程(process):用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。进程需要一些资源才能完成工作,如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是每个CPU核心任何时间内仅能运行一项进程。进程的局限是创建、撤销和切换的开销比较大。

线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。

协程(coroutine):又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。

Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程,同时它可以运行在一个或多个线程上。

在Go中生成一个Goroutine的方式非常的简单:只要在函数前面加上go就生成了。

func number() {
    for i := 0; i < ; i++ {
        fmt.Printf("%d ", i)
    }
}


func main() {
   go number() // 启动一个goroutine
   number()
}

二、协程池解决?

回到开头的问题,如何控制Goroutine的数量?相信有过开发经验的人,第一想法是生成协程池,通过协程池控制连接的数量,这样每次连接都从协程池里去拿。在Golang开发中需要协程池吗?这里分享下知乎有个相关点赞最高的回答:

显然不需要,goroutine的初衷就是轻量级的线程,为的就是让你随用随起,结果你又搞个池子来,这不是脱裤子放屁么?你需要的是限制并发,而协程池是一种违背了初衷的方法。池化要解决的问题一个是频繁创建的开销,另一个是在等待时占用的资源。goroutine 和普通线程相比,创建和调度都不需要进入内核,也就是创建的开销已经解决了。同时相比系统线程,内存占用也是轻量的。所以池化技术要解决的问题goroutine 都不存在,为什么要创建 goroutine pool 呢?如果因为 goroutine 持有资源而要去创建goroutine pool,那只能说明代码的耦合度较高,应该为这类资源创建一个goroutine-safe的对象池,而不是把goroutine本身池化。

在我们日常大部分场景下,不需要使用协程池。因为Goroutine非常轻量,默认2kb,使用go func()很难成为性能瓶颈。当然一些极端情况下需要追求性能,可以使用协程池实现资源的复用,例如FastHttp使用协程池性能提高许多。

当然现在我们如果需要使用Goroutine池也不需要重复造轮子了,目前github上已经有开源的项目ants来实现 Goroutine 池。ants已经实现了对大规模 Goroutine 的调度管理、Goroutine 复用,允许使用者在开发并发程序的时候限制 Goroutine 数量,复用资源,达到更高效执行任务的效果。

项目地址:https://github.com/panjf2000/ants

三、 通过channel和sync方式限制协程数量

3.1 Channel

Goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么Goroutine之间如何进行数据的通信呢?Go提供了一个很好的通信机制channel,channel可以与 Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make创建channel。

3.2 Sync

Go语言中有一个sync.WaitGroup,WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。下面示例代码中wg.Wati会阻塞代码的运行,直到计数器值为0。

通过Golang自带的channel和sync,可以实现需求,下面代码中通过channel控制Goroutine数量。

package main


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


type Glimit struct {
  n int
  c chan struct{}
}


// initialization Glimit struct
func New(n int) *Glimit {
  return &Glimit{
    n: n,
    c: make(chan struct{}, n),
  }
}
// Run f in a new goroutine but with limit.
func (g *Glimit) Run(f func()) {
  g.c <- struct{}{}
  go func() {
    f()
    <-g.c
  }()
}


var wg = sync.WaitGroup{}


func main() {
  number := 10
  g := New(2)
  for i := 0; i < number; i++ {
    wg.Add(1)
    value :=i
    goFunc := func() {
      // 做一些业务逻辑处理
      fmt.Printf("go func: %d\n", value)
      time.Sleep(time.Second)
      wg.Done()
    }
    g.Run(goFunc)
  }
  wg.Wait()
}

四、总结

在文章的开头通过在服务器模拟Goroutine数量太多导致系统资源上升,提醒大家避免这类问题。当然每个人可根据自己所在的场景选择最合适的方案,有时候成熟的第三方库也是个很好的选择,可以避免重复造轮子。

下面有两个思考问题,大家可以尝试着去思考一下。

思考1:为什么我们要使用sync.WaitGroup?

这里如果我们不使用sync.WaitGroup控制的话,原因出在当主程序结束时,子协程也是会被终止掉的。因此剩余的 goroutine 没来及把值输出,程序就已经中断了

思考2:代码中channel数据结构为什么定义struct,而不定义成bool这种类型呢?

因为空结构体变量的内存占用大小为0,而bool类型内存占用大小为1,这样可以更加最大化利用我们服务器的内存空间。

func main(){
  a :=struct{}{}
  b := true
  fmt.Println(unsafe.Sizeof(a))  # println 0
  fmt.Println(unsafe.Sizeof(b))  # println 1
}

【推荐阅读】

《携程架构实践》

京东

当当

《携程人工智能实践》

京东

当当

 “携程技术”公众号

  分享,交流,成长

go并发日记·深入理解go语言channel与goroutine阻塞 会当凌绝顶,一览众山小。 本文是自己对相关知识运用和理解的一些领悟,希望给大家带来帮助! go中的通道就是channel,用于goroutine之间的通讯/数据的传递。可以想象成一个管,一端有人操作发送,一端有人操作接收,如果发送端没有准备好不能发送,即使接收端能正常接收那也不能拿到数据,发送端能正常发送但是接收端不能正常接收,那么整个流程也是不流通的。只有两端都能保证正常发送和接收,数据才能... 阅读详情

相关推荐

go并发日记·常用并发场景中的并发控制思路与方案

以下涉及讨论的情景均建立在代码中进行了并发的基础上。 控制阻塞 问题来了:为什么要阻塞? 这个问题比较基础,因为代码中你虽然使用go关键字创建了goroutine,但实际上绝大对数情况下执行最快的还是main线(主线程),这时候其他地方你写的再多也是 场景1:使用了goroutine来进行并发,希望持续进行不退出 思路:使用select{}阻塞、for循环阻塞 场景2:使用了goroutine来进行并发,希望执行完任务后退出 思路: 1,使用select{}阻塞(监听ch,逻辑执行完后给ch写入数

空山新雨后,天气晚来秋 1452

控制goroutine 的并发执行数量

make(chan struct{}, 300) 创建缓冲区大小为 300 的 channel,在没有被接收的情况下,至多发送 300 个消息则被阻塞。限制太少,会大大增加程序执行完成需要的时间,具体限制多少,需要权衡,太大太小可能都有问题)对单个file/socket的并发操作数超过了系统上限,这个是标准输出造成的,具体一点,就是文件句柄数量达到限制。(而休眠10几s的代码,内存到不了这么大,就已经因为fmt的问题panic了)开启协程前,调用 ch <- struct{}{},若缓存区满,则阻塞。

techdashen的博客 1277

论go语言如何并发安全的使用map的问题分析

论go语言如何并发安全的使用map的问题分析

空山新雨后,天气晚来秋 1143

go并发控制--控制goroutine数量

在 Go 语言中创建协程(Goroutine)的成本非常低,因此稍不注意就可能创建出大量的协程,一方面会造成资源的浪费,另一方面不容易控制这些协程的状态。 主要内容如下: package main import ( "fmt" "time" ) var wp chan struct{}//定义全局变量channel,用空结构体代表资源 func service(i int){ select { case wp<- struct{}{}://一个请求表示占用一个资...

Steve的博客 1217

关于go中资源泄漏/goroutine泄漏/内存泄漏/CPU打满等情况分析

go中资源泄漏/goroutine泄漏/内存泄漏/CPU打满等情况分析

空山新雨后,天气晚来秋 646

Index-TTS-1.5:多模态语音合成的技术演进与商业落地实践

本文深入探讨了Index-TTS-1.5在多模态语音合成领域的技术演进与商业落地。该模型基于GPT风格的自回归Transformer架构,通过优化条件编码器和引入字符-拼音混合建模,显著提升了语音生成的稳定性、精准度和真实感,实现了高质量的零样本语音克隆。文章还详细介绍了其云端体验、本地WebUI部署及ComfyUI集成等实战路径,并分析了其在虚拟数字人、个性化内容创作等商业场景中的应用潜力与挑战。

weixin_29098367的博客 401

go语言打印日期_判定是否掌握Go语言的最重要标准:对并发的掌握

并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因。Go语言中的并发编程并发与并行并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。并行:同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并...

weixin_39561577的博客 282

从工创赛实战到开源:一个高鲁棒性智能物流视觉系统的构建与优化

本文分享了从工创赛实战中构建高鲁棒性智能物流视觉系统的完整经验。针对赛场复杂光线环境,详细阐述了摄像头选型、基于CLAHE和形态学梯度的抗干扰色环识别算法、卡尔曼滤波状态估计以及多线程串口通讯与任务调度等核心优化策略,旨在打造一个稳定可靠的视觉系统,并已将相关代码开源。

weixin_29046035的博客 317

go channel 缓冲区最大限制_go语言网络编程token bucket算法简单实现

bucket token 令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。图片来源于网络首先我们有一个bucket,里面存放了n个token,每次当有一个网络请求时,我们发一个token,当这个请求结束时,我们再将其token收回放入bucket中。这样我们的请求数量就不会同时超过桶内的token的数量。可能有人想,我们创建一...

weixin_35426345的博客 232

先进封装技术 Part03---重布线层(RDL)的科普

RDL是一种在芯片封装过程中用于重新分布电气连接的技术。它通过在芯片表面或中介层上形成额外的布线层,重新分配芯片的I/O(输入/输出)位置,以适应不同的封装需求和提高电气连接的灵活性。

阿拉伯梳子的专栏 4700

基于R语言的DICE模型实践技术应用;预测不同减排路径下的经济增长、碳排放量、气候变化程度以及社会福利变化

DICE模型是一个动态综合气候经济模型,由诺贝尔经济学奖得主William Nordhaus开发,用于评估气候变化对经济的影响以及不同减排政策的经济成本和效益。以下是一个关于DICE模型在气候变化影响评估中的实际应用案例,结合R语言代码进行讲解。

KY_chenzhao的博客 866

go 线程限制数量v2 --chatGPT

在这个示例中,A(`makeNames`)是一个 HTTP 请求处理程序,它接收请求并将请求放入通道中,然后立即返回而不等待任务完成。names := []string{"Worker1", "Worker2", "Worker3", "Worker4", "Worker5"} // 生成 names。通过这种方式,A 和 B 可以并发运行,不会相互阻塞,即使每秒的请求量非常大,也能够高效处理。2. **请求队列:** 使用一个请求队列,A 将请求放入队列,而 B 从队列中取出请求并执行相应的任务。

Fly_Sky 410

联想X3650M5服务器双模式切换实战:UEFI与Legacy BIOS如何选择?

本文深入探讨了联想X3650 M5服务器在UEFI与Legacy BIOS启动模式间的选择策略。文章从技术原理、决策矩阵、实战切换步骤及后期验证等方面,为企业IT管理员提供了基于操作系统兼容性、存储架构、安全合规与运维习惯的完整决策框架,帮助其在数据中心硬件生命周期管理中做出明智选择。

weixin_29207533的博客 281

go channel 缓冲区最大限制_干货 | Go开发中,如何有效控制Goroutine的并发数量

作者简介洋洋,携程高级安全研发工程师,擅长Python、Golang开发,负责安全工具研发。相信大家在学习Go的过程中,都会看到类似这样一句话:"与传统的系统级线程和进程相比,协程的最大优势在于其‘轻量级’,可以轻松创建上百万个而不会导致系统资源衰竭"。那是不是意味着我们在开发过程中,可以随心所欲的调用协程,而不关心它的数量呢?答案当然是否定的。我们在开发过程中,如果不对Gorouti...

weixin_39539733的博客 247

基于图神经网络强化学习解决车辆路径规划问题(完整代码+报告)

基于图神经网络强化学习解决车辆路径规划问题(完整代码+报告)

3055

华为OD机试双机位C卷-几何平均值最大子数组(Py/Java/C/C++/Js/Go)

华为OD机试(机考)2026双机位C卷 【几何平均值最大子数组】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【几何平均值最大子数组】。

qq_45776114的博客 1367

golang高级进阶(一):进程、线程、并发、并行、goroutine协程

目录 一、关于进程和线程 1. 进程(Process) 2. 线程 二、关于并发和并行 1. 并发 2. 并行 3. 通俗讲 三、goroutine协程以及主线程 1. 主线程 2. 协程 3. 多协程和多线程 四、go关键词实操 1. 顺序执行 2. 加入go 3. 加入时间 4. 主线程执行快的情况 5. sync.WaitGroup解决不等待 6. 多协程并发并行执行 五、设置golang运行时占用的cpu核数量(不是很重要) 重点:这一篇全是干货,一定要多

genziisme的专栏 6908

基于LED可见光通信的物联网系统设计_344

随着物联网技术的不断发展,传统无线通信在工业环境中面临电磁干扰强、通信质量不稳定等问题。为此,本文设计了一种基于LED可见光通信的物联网系统,实现工厂车间内设备状态的稳定、高效、低干扰数据传输。系统主要由设备节点、设备主机、华为云物联网服务器以及可视化上位机构成。 设备节点采集机器的出货量、运行状态、机身温度等关键信息,通过调制后的LED可见光信号发送至设备主机。主机利用红外接收器接收光信号后进行解调处理,并通过WIFI模块将数据上传至华为云IOT服务器。

344

如何使用 Go 更好地开发并发程序,纯干货!

Go 语言的并发特性是其一大亮点,今天我们来带着大家一起看看如何使用 Go 更好地开发并发程序?我们都知道计算机的核心为 CPU,它是计算机的运算和控制核心,承载了所有的计算任务。最近半个...

aoho求索 513

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

干货分享丨从MPG 线程模型,探讨Go语言的并发程序

摘要:Go 语言的并发特性是其一大亮点,今天我们来带着大家一起看看如何使用 Go 更好地开发并发程序。 我们都知道计算机的核心为 CPU,它是计算机的运算和控制核心,承载了所有的计算任务。最近半个世纪以来,由于半导体技术的高速发展,集成电路中晶体管的数量也在大幅度增长,这大大提升了 CPU 的性能。著名的摩尔定律——“集成电路芯片上所集成的电路的数目,每隔18个月就翻一番”,描述的就是该种情形。 过于密集的晶体管虽然提高了 CPU 的处理性能,但也带来了单个芯片发热过高和成本过高的问题,与此同时,受限于

华为云官方博客 2068

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

转:Go 为什么这么“快”,G-P-M模型讲的最好的干货

之前看过很多go语言的G-P-M调度模型的文章,看的迷迷糊糊,知道看到这篇腾讯出的,大厂就是大厂。 怎么让我们的系统更快 随着信息技术的迅速发展,单台服务器处理能力越来越强,迫使编程模式由从前的串行模式升级到并发模型。 并发模型包含 IO 多路复用、多进程以及多线程,这几种模型都各有优劣,现代复杂的高并发架构大多是几种模型协同使用,不同场景应用不同模型,扬长避短,发挥服务器的最大性能。 而多线程,因为其轻量和易用,成为并发编程中使用频率最高的并发模型,包括后衍生的协程等其他子产品,也都基于它。 并发 ≠ 并

熹微及至 1151

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

16.3Go语言干货-并发编程之池与select

1.goroutine池(worker pool) 在平常工作中我们通常会指定启动goroutine数量 采用worker pool 模式,控制goroutine的数量,防止goroutine泄漏和暴涨。 package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker:%d

apple_56973763的博客 154

中清空指定select_Golang中select语句的实现(干货)

select语句介绍select 是 Go 中的一个控制结构。select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。但是 select 语句中所有 case 中的表达式都必须是 channel 的发送或接收操作。select 语句的语法结构和switch语句很相似,也有case语句和defau...

weixin_28999139的博客 355

硬核吃透|进程、线程、Go GMP调度模型(彻底通透,面试绝杀)

很多开发者始终搞不懂:进程、线程、协程的本质区别,也无法理解 Go 语言 GMP 调度模型的核心优势和底层逻辑。 本文**从操作系统底层从零切入**,完全贴合你全程提问的核心逻辑,层层递进梳理:进程 → 系统线程 → CPU调度 → 上下文切换 → Go GMP 模型,打通并发编程所有底层壁垒,无废话、全干货,适配实战开发\+面试背诵。

2401_84169006的博客 523

构建企业级Linux安全审计平台:架构设计与工程实践

在当今的企业IT环境中,系统安全审计与合规性管理是保障业务连续性的基础技术环节。其核心原理在于通过自动化手段持续收集、分析系统配置与行为数据,并与预定义的安全基线进行比对。这一过程的技术价值在于将传统人工、离散的安全检查转变为系统化、可度量的主动防御体系,显著提升风险发现与响应的效率。从应用场景看,无论是满足等保合规要求、防范内部威胁,还是应对软件供应链安全风险,自动化审计平台都已成为现代安全运维的必备基础设施。本文聚焦于如何设计并实现一个集资产发现、基线核查、漏洞管理、日志分析于一体的集中式Linux安全

weixin_30292843的博客 412
上一篇: 干货 | 为了给你更好的体验,携程做了个“一站式”客服机器人
下一篇: 干货 | 查询耗时降低2/3,携程度假搜索引擎架构优化
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值