首页 > 其他分享 >Go Goroutine 究竟可以开多少?(详细介绍)

Go Goroutine 究竟可以开多少?(详细介绍)

时间:2024-06-16 23:30:41浏览次数:13  
标签:详细 fmt Goroutine 并发 func Go runtime

Go Goroutine 究竟可以开多少?

Go语言因其高效的并发处理能力而备受欢迎,而Goroutine则是Go语言实现并发编程的核心。Goroutine比传统的线程更加轻量,允许开发者轻松地处理大量并发任务。那么,Go语言中的Goroutine究竟可以开多少呢?在回答这个问题之前,我们需要先了解两个关键问题:

  1. Goroutine是什么?
  2. 开 Goroutine 需要消耗什么资源?因为最终的资源上限就决定了程序可以开多少Goroutine。

一、什么是Goroutine?

Goroutine是Go语言中的一种轻量级线程。与操作系统线程不同,Goroutine的创建和销毁成本极低。它们由Go运行时(runtime)管理,使用协作式调度(cooperative scheduling)来实现高效的并发处理。Goroutine的启动非常简单,只需要在函数调用前加上go关键字即可。

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello, Goroutine!")
}

func main() {
    go sayHello()
    time.Sleep(time.Second)
}

在上述示例中,sayHello函数被作为一个Goroutine启动。

Goroutine的优势

  1. 轻量级:Goroutine比传统的操作系统线程要轻量得多。每个Goroutine大约只占用几KB的栈空间,并且栈是动态增长的。
  2. 简单易用:Goroutine的创建非常简单,只需要一个go关键字。
  3. 高效的调度:Go运行时通过M:N调度模型管理Goroutine,将M个Goroutine映射到N个操作系统线程上。这使得Goroutine可以在多核处理器上高效运行。

Goroutine的数量限制

理论上限

在理论上,Goroutine的数量几乎是无限的。由于每个Goroutine只占用少量内存,现代计算机的内存资源足以支持数百万甚至更多的Goroutine。然而,实际能开的Goroutine数量还受到其他因素的影响:

  1. 内存限制:每个Goroutine需要分配栈空间,虽然初始栈空间很小,但随着函数调用深度增加,栈空间会动态增长。内存限制是影响Goroutine数量的主要因素之一。
  2. CPU调度开销:虽然Goroutine的调度开销比操作系统线程低,但在极大量的Goroutine并发运行时,调度开销仍然不可忽视。
  3. 系统资源限制:操作系统和Go运行时对资源的管理也会影响Goroutine的实际数量。

实践中的经验

在实践中,Goroutine的数量通常不需要达到极限。以下是一些常见的经验和最佳实践:

  1. 合理设计并发:根据实际需求设计并发任务的数量,避免盲目创建大量Goroutine。
  2. 使用sync.WaitGroup管理并发:通过sync.WaitGroup可以方便地等待一组Goroutine完成。
  3. 监控和调优:使用Go语言提供的性能分析工具(如pprof)监控Goroutine的运行状况,及时发现和解决性能瓶颈。

代码示例:创建大量Goroutine

下面的示例代码演示了如何创建大量Goroutine,并观察其运行情况:

package main

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

func main() {
    var wg sync.WaitGroup
    numGoroutines := 100000

    wg.Add(numGoroutines)
    for i := 0; i < numGoroutines; i++ {
        go func(i int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d\n", i)
        }(i)
    }

    wg.Wait()
    fmt.Println("All goroutines finished")

    // 打印当前运行的Goroutine数量
    fmt.Printf("Number of goroutines: %d\n", runtime.NumGoroutine())
}

在这段代码中,我们创建了10万个Goroutine,并使用sync.WaitGroup等待所有Goroutine完成。运行这段代码时,可以观察到系统资源的使用情况。

二. 开Goroutine需要消耗什么资源?

1. 内存的消耗

每个Goroutine需要消耗一定的内存,因为它们需要存储相应的数据结构。我们可以通过实验来测量开启Goroutine的内存消耗。

package main

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

func getGoroutineMemConsume() {
    var c chan int
    var wg sync.WaitGroup
    const goroutineNum = 1000000

    memConsumed := func() uint64 {
        runtime.GC() // GC,排除对象影响
        var memStat runtime.MemStats
        runtime.ReadMemStats(&memStat)
        return memStat.Sys
    }

    noop := func() {
        wg.Done()
        <-c // 防止Goroutine退出,内存被释放
    }

    wg.Add(goroutineNum)
    before := memConsumed() // 获取创建Goroutine前的内存
    for i := 0; i < goroutineNum; i++ {
        go noop()
    }
    wg.Wait()
    after := memConsumed() // 获取创建Goroutine后的内存

    fmt.Println(runtime.NumGoroutine())
    fmt.Printf("%.3f KB bytes\n", float64(after-before)/goroutineNum/1024)
}

func main() {
    getGoroutineMemConsume()
}

结果分析:每个Goroutine大约消耗2KB的空间。假设计算机的内存是2GB,那么理论上最多可以开启2GB / 2KB ≈ 1百万个Goroutine。

2. CPU的消耗

Goroutine的调度和任务执行都会占用CPU资源。具体消耗的CPU资源取决于Goroutine执行的任务。如果任务是CPU密集型的计算,消耗的CPU资源会更多。

衡量一段代码能开多少协程同时并发运行,还需要考虑程序内的任务类型。如果是非常消耗内存的网络操作,可能几个Goroutine就能跑崩溃。如果是CPU密集型任务,可能几百个Goroutine就会让程序达到瓶颈。

三. Goroutine的实际应用

1. 如何控制并发数?

使用runtime.NumGoroutine()可以监控当前Goroutine的数量。为了避免过多的Goroutine导致资源耗尽,我们可以通过一些方法来控制并发数。

方法一:保证任务只有一个Goroutine在运行

可以通过设置一个运行标志(running flag)来确保同一时间只有一个Goroutine在运行某个任务。

type SingerConcurrencyRunner struct {
    isRunning bool
    sync.Mutex
}

func NewSingerConcurrencyRunner() *SingerConcurrencyRunner {
    return &SingerConcurrencyRunner{}
}

func (c *SingerConcurrencyRunner) markRunning() (ok bool) {
    c.Lock()
    defer c.Unlock()
    if c.isRunning {
        return false
    }
    c.isRunning = true
    return true
}

func (c *SingerConcurrencyRunner) unmarkRunning() {
    c.Lock()
    defer c.Unlock()
    c.isRunning = false
}

func (c *SingerConcurrencyRunner) Run(f func()) {
    if !c.markRunning() {
        return
    }
    go func() {
        defer func() {
            if err := recover(); err != nil {
                // log error
            }
            c.unmarkRunning()
        }()
        f()
    }()
}
方法二:任务有指定的协程数运行

通过限制Goroutine的数量来控制并发。例如,使用带缓冲的通道来控制并发数。

type ProcessFunc func(ctx context.Context, param interface{})

type MultiConcurrency struct {
    ch chan struct{}
    f  ProcessFunc
}

func NewMultiConcurrency(size int, f ProcessFunc) *MultiConcurrency {
    return &MultiConcurrency{
        ch: make(chan struct{}, size),
        f:  f,
    }
}

func (m *MultiConcurrency) Run(ctx context.Context, param interface{}) {
    m.ch <- struct{}{}
    go func() {
        defer func() {
            <-m.ch
            if err := recover(); err != nil {
                fmt.Println(err)
            }
        }()
        m.f(ctx, param)
    }()
}

func mockFunc(ctx context.Context, param interface{}) {
    fmt.Println(param)
}

func main() {
    concurrency := NewMultiConcurrency(10, mockFunc)
    for i := 0; i < 1000; i++ {
        concurrency.Run(context.Background(), i)
        if runtime.NumGoroutine() > 13 {
            fmt.Println("goroutine", runtime.NumGoroutine())
        }
    }
}

通过这种方式,可以有效控制同时运行的Goroutine数量,防止内存和CPU资源被耗尽。

四. 常见的问题

1. Too many open files

如果程序中有大量打开的文件或socket没有及时关闭,可能会遇到"too many open files"的错误。这时需要检查任务中是否有大量打开的文件或socket连接,并确保它们在不需要时及时关闭。

2. Out of memory

如果Goroutine泄露,即不断创建Goroutine但没有结束,可能会导致内存耗尽。需要确保Goroutine能够正常退出,并在适当的时候进行垃圾回收。

结论

Goroutine是Go语言并发编程的强大工具,其轻量级和高效的特点使得Go程序可以轻松处理大量并发任务。虽然理论上Goroutine的数量几乎没有限制,但在实际应用中,我们仍需考虑内存、CPU调度开销和系统资源限制等因素。合理设计并发任务、监控和调优是确保系统稳定性和高效性的关键。

希望本文能帮助你更好地理解Goroutine的并发能力,并在实际项目中有效利用这一强大特性。

标签:详细,fmt,Goroutine,并发,func,Go,runtime
From: https://blog.csdn.net/qq_47831505/article/details/139724542

相关文章

  • React 使用 Zustand 详细教程
    前言Redux、MobX和ContextAPI等技术的存在,使得管理大型应用的状态变得更加可行。本教程要深入探讨的是Zustand——一个极简且高效的状态管理库,详细介绍如何在React项目中使用Zustand来管理状态。什么是Zustand?Zustand是一个简单、小体积(只有不到1kB)且性能优......
  • 如何从0到1实现一个go语言代码项目
    创建一个Go语言项目是一个逐步的过程,这里我将为你提供一个简单的项目示例,包括一些基础步骤来帮助你从0开始实现一个Go语言项目。步骤1:安装Go语言环境首先,你需要在你的计算机上安装Go语言环境。你可以从[Go官网](https://golang.org/dl/)下载适合你操作系统的安装包。......
  • Parallels DeskTop 19软件最新版下载【安装详细图文教程】
    ParallelsDesktop是一款专为Mac设计的虚拟机软件,它允许用户在Mac上同时运行Windows、Linux等多个操作系统,而无需额外的硬件设备。通过ParallelsDesktop,Mac用户可以轻松地在同一台电脑上体验不同操作系统的功能和应用程序。​安装包获取地址:ParallelsDesktopfor......
  • 基于Typora、Gitee和picgo搭建图床
    基于Typora、Gitee和picgo搭建图床使用Typora编辑文本上传图片的时候,会发现图片都是保存在本地的,如果上传到博客图片会显示不出来,还需要自己手动一张一张往上贴,怎么解决?(1)首先下载一个picgo链接:https://pan.baidu.com/s/1Uf5BH7EegbhcLJ-CwUpceQ?pwd=ezta提取码:ezta......
  • Trusty qemu + android环境搭建详细步骤
    下载源码mkdirtrustycdtrustyrepoinit-uhttps://android.googlesource.com/trusty/manifest-bmasterreposync-j32编译./trusty/vendor/google/aosp/scripts/build.pygeneric-arm64查看编译结果lsbuild-root/build-generic-arm64/lk.bin安装运行依赖sud......
  • 学生管理系统(超详细教程+源码)
    学生管理系统1.前言1.1项目地址本项目共有前后两端地址(使用Docker部署,也可以自己部署到本地)。前端:SSE-DZH/MS-Vue:MS前端vue(github.com)后端:SSE-DZH/MS-Spring:MS后端(github.com)2.开发环境2.1基础环境JDK版本:17MySQL版本:8.0.36Redis版本:3.2.100......
  • GPU版PyTorch安装、GPU版TensorFlow安装(详细教程)
    目录一、介绍PyTorch、TensorFlow 1. PyTorch2.TensorFlow二、GPU版PyTorch安装1.确定CUDA版本2.确定python版本3.安装PyTorch3.1使用官网命令安装(速度慢)3.2本地安装(速度快)4.检验是否安装成功三、GPU版TensorFlow安装1.确定CUDA版本2.确定TensorFlow版本3.安......
  • 史上最详细的轨迹优化教程-机器人避障及轨迹平滑实现(干货满满)
    有一些朋友问我到底如何用优化方法实现轨迹优化(避障+轨迹平滑等),今天就出一个干货满满的教程,绝对是面向很多工业化场景的讲解,为了便于理解,我选用二维平面并给出详细代码实现,三维空间原理相似。本教程禁止转载,主要是有问题可以联系我探讨,我的邮箱[email protected]下面......
  • (pdf)数据结构与算法分析 Java语言描述=Data Structures and Algorithm Analysis in Jav
    书:pan.baidu.com/s/1tGbGhhQ3Ez1SIkqdEREsjQ?pwd=eqp0提取码:eqp0数组:作为最基本的数据结构,用于存储固定大小的同类型元素集合。链表:动态数据结构,允许在任意位置插入和删除元素。栈:后进先出(LIFO)的数据结构,常用于函数调用和表达式求值。队列:先进先出(FIFO)的数据结构,常用于任务调......
  • CMU最新论文:机器人智慧流畅的躲避障碍物论文详细讲解
    CMU华人博士生TairanHe最新论文:AgileButSafe:LearningCollision-FreeHigh-SpeedLeggedLocomotion代码开源:Code:https://github.com/LeCAR-Lab/ABSB站实际效果展示视频地址:bilibili效果地址我会详细解读论文的内容,让我们开始吧。敏捷且安全:学习无碰撞的高速腿......