Golang根据job数量动态控制每秒协程的最大创建数量方法详解

 更新时间:2024年01月19日 08:48:27   作者:Golang在发光  
这篇文章主要介绍了Golang根据job数量动态控制每秒协程的最大创建数量方法

需求

第三方的接口,限制接口请求的QPS,每秒5次

需要控制job「访问接口」的次数,每秒不能同时超过5次,包括 进行中的任务、刚启动的任务

使用限流器来控制任务的启动频率

要确保单位时间内(例如每秒)运行的任务数量不超过特定的上限(如5个任务),并且在任务执行完成得很快时,考虑已完成的任务和正在执行的任务作为正在运行的任务总数,可以使用限流器来控制任务的启动频率,并结合使用信号量来管理同时运行的任务数量。

具体来说,使用一个信号量来限制同时进行的任务数量,并且在任务完成时,仅在下一秒钟允许新的任务开始,以确保即使某些任务快速完成,也不会在同一秒钟内启动超过限制数量的任务

package main
import (
    "context"
    "fmt"
    "math/rand"
    "sync"
    "sync/atomic"
    "time"
    "golang.org/x/time/rate"
)
func RateLimit() {
    const maxJobsPerSecond = 5
    const numJobs = 22
    var wg sync.WaitGroup
    // 计数器
    var runningJobs int32 // 当前正在执行的任务数量
    var startedJobs int32 // 启动后的任务数量
    var finishedJobs int32 // 刚完成的任务数量
    limiter := rate.NewLimiter(rate.Every(time.Second/time.Duration(maxJobsPerSecond)), maxJobsPerSecond)
    semaphore := make(chan struct{}, maxJobsPerSecond)
    for i := 1; i <= numJobs; i++ {
        wg.Add(1)
        go func(jobID int) {
            defer wg.Done()
            limiter.Wait(context.Background()) // 等待限流器允许进行下一个任务
            semaphore <- struct{}{} // 获取信号量
            atomic.AddInt32(&startedJobs, 1)
            atomic.AddInt32(&runningJobs, 1)
            executeJob(jobID) // 执行任务
            atomic.AddInt32(&finishedJobs, 1)
            atomic.AddInt32(&runningJobs, -1)
            <-time.After(time.Second) // 等待一秒钟后释放信号量
            <-semaphore
            // 打印当前状态
            printStatus(&runningJobs, &startedJobs, &finishedJobs)
        }(i)
    }
    wg.Wait()
    fmt.Println("所有工作完成")
}

注意事项

  • 限流器rate.NewLimiter用于控制任务启动的频率,以确保每秒不超过maxJobsPerSecond个任务开始执行。
  • 使用信号量semaphore来控制同时进行的任务数量。
  • 为了确保在任何一秒内同时进行的任务数量不超过限制,在任务完成后等待一秒钟,然后再释放信号量。这样做可以保证即使任务很快完成,也不会立即启动新的任务。

这种实现方式确保了即使任务执行得很快,每秒钟启动的新任务数量也不会超过限制,并且同时考虑了正在执行和刚刚完成的任务。

动态创建协程

  • 协程的启动是动态的。在代码中,每个任务对应于一个动态创建的协程。这些协程是在循环中根据任务数量(numJobs)动态生成的。
  • 具体来说,每当有一个新的任务需要执行时,都会创建一个新的协程来处理这个任务。这是通过在main函数的循环中调用go关键字实现的。这个过程在每次循环迭代中发生,从而为每个任务动态创建一个新的协程

由于使用了限流器(rate.Limiter),这些协程不是一次性全部创建,而是根据限流器允许的速率逐个创建。每个协程在开始执行任务之前会等待限流器的许可,以此确保每秒启动的任务数量不超过设定的最大值

func executeJob(jobID int) {
  startTime := time.Now() // 记录任务开始时间
    // 模拟任务执行时间
    fmt.Printf("%v Job %d started\n",time.Now().Format("2006-01-02 15:04:05.000"), jobID)
  // 初始化随机数种子
  rand.Seed(time.Now().UnixNano())
  // 随机生成一个时间间隔(例如,1到5000毫秒之间)
  min := 1
  max := 5000
  duration := time.Duration(rand.Intn(max-min+1)+min) * time.Millisecond
  time.Sleep(duration)
  durationCost := time.Since(startTime) // 计算任务耗时
    fmt.Printf("%v Job %d finished Cost:%v\n", time.Now().Format("2006-01-02 15:04:05.000"),jobID, durationCost)
}
func printStatus(runningJobs, startedJobs, finishedJobs *int32) {
    fmt.Printf("Current status - Running: %d, Started: %d, Finished: %d\n",
        atomic.LoadInt32(runningJobs),
        atomic.LoadInt32(startedJobs),
        atomic.LoadInt32(finishedJobs))
}

可以在代码中添加额外的逻辑来跟踪和打印正在执行、进行中、刚启动和刚完成的任务数量。使用原子操作(来自sync/atomic包)来确保在并发环境下对这些计数器的操作是安全的。

在这个示例中:

  • 使用sync/atomic包中的AddInt32和LoadInt32来安全地增加和读取计数器的值。

  • 在每个任务开始时,增加startedJobs和runningJobs计数器。

  • 在每个任务完成时,增加finishedJobs计数器,并减少runningJobs计数器。

  • 在任务完成后和释放信号量前,打印当前的任务状态。

注意事项

  • 这种方法可以帮助我们跟踪不同状态下的任务数量。

  • 使用原子操作确保在并发环境中对计数器的读写是安全的。

  • printStatus函数在每个任务的结束时被调用,以打印当前的任务状态

以上就是Golang根据job数量动态控制每秒协程的最大创建数量方法详解的详细内容,更多关于Golang job控制协程创建数的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法

    Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法

    对音视频一直是小白,决定沉下心来,好好研究一下音视频知识,下面这篇文章主要给大家介绍了关于Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法,需要的朋友可以参考下
    2022-12-12
  • Go语言数据结构之希尔排序示例详解

    Go语言数据结构之希尔排序示例详解

    这篇文章主要为大家介绍了Go语言数据结构之希尔排序示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • go sync包中的互斥锁Mutex和等待组WaitGroup使用详解

    go sync包中的互斥锁Mutex和等待组WaitGroup使用详解

    这篇文章主要为大家介绍了go sync包中的互斥锁Mutex和等待组WaitGroup使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Go语言中错误处理实例分析

    Go语言中错误处理实例分析

    这篇文章主要介绍了Go语言中错误处理,实例分析了Go语言中针对错误处理的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Go语言使用net/http构建一个RESTful API的示例代码

    Go语言使用net/http构建一个RESTful API的示例代码

    Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如 Gin、Echo)已经封装了很多功能,但如果你真正想理解 Go的底层Web服务机制,那么使用net/http实现一个原生的RESTful API 是最好的入门方式,需要的朋友可以参考下本文
    2025-08-08
  • 详解Golang官方中的一致性哈希组件

    详解Golang官方中的一致性哈希组件

    这篇文章主要为大家详细介绍了Golang官方中的一致性哈希组件的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-04-04
  • Golang实现程序优雅退出的方法详解

    Golang实现程序优雅退出的方法详解

    项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,Golang如何让程序做到优雅的退出?本文就来详细为大家讲讲
    2022-06-06
  • 一文教你学会Go中singleflight的使用

    一文教你学会Go中singleflight的使用

    缓存在项目中使用应该是非常频繁的,提到缓存只要了解过 singleflight ,基本都会用于缓存实现的一部分吧,下面就跟随小编一起来学习一下singleflight的使用吧
    2024-02-02
  • Go语言使用字符串的几个技巧分享

    Go语言使用字符串的几个技巧分享

    这篇文章中小编将给出一些Go语言在处理字符串方面的技巧,对大家学习Go语言具有一定的参考借鉴价值,下面一起看看吧。
    2016-09-09
  • go程序中同一个包下为什么会存在多个同名的函数或变量(详细解析)

    go程序中同一个包下为什么会存在多个同名的函数或变量(详细解析)

    这篇文章主要介绍了go程序中同一个包下为什么会存在多个同名的函数或变量(详细解析),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-05-05

最新评论