Go语言并发编程之控制并发数量实现实例

 更新时间:2024年01月05日 10:28:38   作者:程序员Aike  
这篇文章主要为大家介绍了Go语言并发编程之控制并发数量实例探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

今天我主要分享下Go语言并发编程如何控制并发数量。

适用场景

有一批数据需要并发处理,不能开启协程数量过多,以免服务器资源耗尽或者对服务造成过大压力,需要控制并发数量为N。

代码

话不多说,直接上代码,示例中采用三种方式进行处理。

1、以int数据集为例,并发数量为num;

2、第一种方式,并发函数报错则终止任务执行。后两种方式会等待所有处理任务执行完,再返回是否发生错误。

代码如下:

# utils.go
package utils
import (
  "context"
  "fmt"
  "sync"
  "golang.org/x/sync/errgroup"
)
// BatchDeal BatchDeal
// TODO int类型待后续修改为泛型T
func BatchDeal(ctx context.Context, records []int, num int, f func(context.Context, int) error) (err error) {
  ch := make(chan int, num)
  go func() {
    select {
    case <-ctx.Done():
      return
    default:
    }
    for _, v := range records {
      ch <- v
    }
    close(ch)
  }()
  errCh := make(chan error, len(records))
  go func() {
    goN(num, func(i int) {
      select {
      case <-ctx.Done():
        errCh <- ctx.Err()
        return
      default:
      }
      for v := range ch {
        if er := f(ctx, v); er != nil {
          errCh <- er
        }
      }
    })()
    // 处理完关闭errCh
    close(errCh)
  }()
  // 有错误就结束或者关闭errCh后执行
  err = <-errCh
  if err != nil {
    fmt.Printf("batch deal fail, err=%v", err)
    return
  }
  fmt.Println("batch deal end")
  return
}
func goN(n int, fn func(int)) func() {
  var wg sync.WaitGroup
  for i := 0; i < n; i++ {
    wg.Add(1)
    go func(i int) {
      fn(i)
      wg.Done()
    }(i)
  }
  return wg.Wait
}
// BatchDeal2 BatchDeal2
// TODO int类型待后续修改为泛型T
func BatchDeal2(ctx context.Context, records []int, num int, f func(context.Context, int) error) (err error) {
  ch := make(chan int, num)
  go func() {
    select {
    case <-ctx.Done():
      return
    default:
    }
    for _, v := range records {
      ch <- v
    }
    close(ch)
  }()
  err = groupN(ctx, num, func(ctx context.Context) error {
    select {
    case <-ctx.Done():
      return ctx.Err()
    default:
    }
    for v := range ch {
      if err := f(ctx, v); err != nil {
        return err
      }
    }
    return nil
  })
  if err != nil {
    fmt.Printf("batch deal fail, err=%v", err)
    return
  }
  fmt.Println("batch deal end")
  return
}
// groupN n为并发数量
func groupN(ctx context.Context, n int, fn func(context.Context) error) error {
  group, ctx := errgroup.WithContext(ctx)
  for i := 0; i < n; i++ {
    group.Go(func() error {
      if err := fn(ctx); err != nil {
        return err
      }
      return nil
    })
  }
  return group.Wait()
}
// BatchDeal3 BatchDeal3
// TODO int类型待后续修改为泛型T
func BatchDeal3(ctx context.Context, records []int, num int, f func(context.Context, int) error) (err error) {
  group, ctx := errgroup.WithContext(ctx)
  // 并发控制channel,并发数量为num
  ch := make(chan struct{}, num)
  for _, v := range records {
    // 元素进channel,并发超过10则阻塞
    ch <- struct{}{}
    vCopy := v
    group.Go(func() error {
      // 释放元素
      defer func() {
        <-ch
      }()
      if err := f(ctx, vCopy); err != nil {
        return err
      }
      return nil
    })
  }
  // 等待执行完毕,全部执行完毕才会结束
  if err = group.Wait(); err != nil {
    fmt.Printf("batch deal failed, err=%v", err)
  }
  fmt.Println("batch deal end")
  return
}
# utils_test.go
package utils
import (
  "context"
  "fmt"
  "testing"
  "time"
  "github.com/stretchr/testify/assert"
)
// TestBatchDeal
func TestBatchDeal(t *testing.T) {
  records := make([]int, 0)
  for i := 1; i < 100; i++ {
    records = append(records, i)
  }
  num := 10
  testAssert := assert.New(t)
  testF := func(ctx context.Context, i int) error {
    fmt.Printf("args=%d", i)
    time.Sleep(time.Duration(i * int(time.Millisecond)))
    return nil
  }
  testFailF := func(ctx context.Context, i int) error {
    fmt.Printf("args=%d", i)
    time.Sleep(time.Duration(i * int(time.Millisecond)))
    var er error
    if i == 10 {
      fmt.Printf("error accour, i=%d\n", i)
      er = fmt.Errorf("err=%d", i)
    }
    return er
  }
  err := BatchDeal(context.Background(), records, num, testF)
  testAssert.Nil(err)
  err2 := BatchDeal(context.Background(), records, num, testFailF)
  testAssert.ErrorContains(err2, "err")
  err3 := BatchDeal2(context.Background(), records, num, testF)
  testAssert.Nil(err3)
  err4 := BatchDeal2(context.Background(), records, num, testFailF)
  testAssert.ErrorContains(err4, "err")
  err5 := BatchDeal3(context.Background(), records, num, testF)
  testAssert.Nil(err5)
  err6 := BatchDeal3(context.Background(), records, num, testFailF)
  testAssert.ErrorContains(err6, "err")
}

以上就是Go语言并发编程之控制并发数量实现实例的详细内容,更多关于Go并发控制的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言学习之函数的定义与使用详解

    Go语言学习之函数的定义与使用详解

    这篇文章主要为大家详细介绍Go语言中函数的定义与使用,文中的示例代码讲解详细,对我们学习Go语言有一定帮助,需要的可以参考一下
    2022-04-04
  • Go语言高效I/O并发处理双缓冲和Exchanger模式实例探索

    Go语言高效I/O并发处理双缓冲和Exchanger模式实例探索

    这篇文章主要介绍了Go语言高效I/O并发处理双缓冲和Exchanger模式实例探索,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Golang定时器的2种实现方法与区别

    Golang定时器的2种实现方法与区别

    这篇文章主要给大家介绍了关于Golang定时器的2种实现方法与区别的相关资料,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 使用go连接clickhouse的实战操作

    使用go连接clickhouse的实战操作

    这篇文章主要给大家介绍了关于使用go连接clickhouse的实战操作,文中通过实例代码介绍的非常详细,对大家学习或者使用go具有一定的参考学习价值,需要的朋友可以参考下
    2023-03-03
  • Go中make函数和append函数的作用详解

    Go中make函数和append函数的作用详解

    本文给大家介绍Go中make函数和append函数的作用详解,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-10-10
  • Go语言编写高可用日志收集脚本

    Go语言编写高可用日志收集脚本

    在分布式系统和微服务架构中,日志是排查问题、审计行为、监控状态的重要来源,本文将编写一个轻量级,高可用的日志收集脚本,有需要的小伙伴可以了解下
    2025-09-09
  • go select编译期的优化处理逻辑使用场景分析

    go select编译期的优化处理逻辑使用场景分析

    select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。接下来通过本文给大家介绍go select编译期的优化处理逻辑使用场景分析,感兴趣的朋友一起看看吧
    2021-06-06
  • Golang对struct字段重新排序优化数据结构性能实践

    Golang对struct字段重新排序优化数据结构性能实践

    这篇文章主要为大家介绍了Golang对struct字段重新排序优化数据结构性能实践,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 一文带你揭秘Go中new()和make()函数的区别和用途

    一文带你揭秘Go中new()和make()函数的区别和用途

    Go(或 Golang)是一种现代、静态类型、编译型的编程语言,专为构建可扩展、并发和高效的软件而设计,它提供了各种内置的函数和特性,帮助开发人员编写简洁高效的代码,在本博客文章中,我们将探讨 new() 和 make() 函数之间的区别,了解何时以及如何有效地使用它们
    2023-10-10
  • Go语言中包导入下划线的作用详细解析

    Go语言中包导入下划线的作用详细解析

    这篇文章主要介绍了Go语言中包导入下划线作用的相关资料,下划线导入可以帮助我们更好地管理初始化逻辑,减少代码的冗余,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-04-04

最新评论