详解Go语言如何利用上下文进行并发计算

 更新时间:2024年02月22日 09:24:09   作者:爱发白日梦的后端  
在Go编程中,上下文(context)是一个非常重要的概念,它包含了与请求相关的信息,本文主要来和大家讨论一下如何在并发计算中使用上下文,感兴趣的可以了解下

在Go编程中,上下文(context)是一个非常重要的概念,它包含了与请求相关的信息,如截止日期和取消信息,以及在请求处理管道中传递的其他数据。在并发编程中,特别是在处理请求时,正确处理上下文可以确保我们尊重和执行请求中设定的限制,如截止时间。

让我们通过一些代码示例来探讨如何在并发计算中使用上下文,以及如何在处理请求时尊重上下文所设定的截止日期和取消要求。

// download 函数用于下载给定 URL 的内容。
func download(ctx context.Context, url string) (string, error) {...}

download 函数尝试获取给定 URL 的内容。然而,需要注意的是,每个 URL 的下载内容可能不同,因此下载所需的时间也可能不同。如果在截止日期之前未能完成 URL 的下载,该函数将返回一个错误(截止日期错误)。

现在,假设我们需要下载许多 URL,并且我们只有有限的时间来完成这些下载。我们可以使用 errgroup 来并发地进行下载,如果超过截止时间,我们将取消所有并发操作。

// downloadAll 函数并发地下载给定 URL 的内容。
func downloadAll(ctx context.Context, urls []string) ([]string, error) {
  results := make([]string, len(urls))

  g, ctx := errgroup.WithContext(ctx)
  for i := range len(urls) {
    g.Go(func() error {
      content, err := download(ctx, urls[i])
      if err != nil {
        return err
      }

      results[i] = content
      return nil
    })
  }

  if err := g.Wait(); err != nil {
    return nil, err
  }

  return results, nil
}

在这个示例中,downloadAll 函数同时下载每个给定的 URL,并将相同的上下文传递给 download 函数。如果下载任何一个 URL 所需的时间超过了设定的截止时间,download 函数将失败,从而导致整个并发流程也失败,downloadAll 将返回一个截止日期错误。

除了下载这些 URL,我们还需要处理下载的内容。例如,我们可能要对每个 URL 的内容应用某个过滤器(谓词)。

// filter 函数检查给定内容是否符合给定的谓词。
func filter(content string, pred func(string) bool) bool {
  return pred(content)
}

请注意,过滤器既不需要上下文,也不进行任何跨边界调用。过滤器函数不关心上游处理的截止日期。

使用 filter 函数,我们可以定义一个过滤所有内容的函数。

// filterAll 函数同时过滤所有给定的内容。
func filterAll(contents []string, pred func(string) bool) []string {
  type Result struct {
    content string
    ok      bool
  }

  results := make([]Result, len(contents))

  g := errgroup.Group{}
  for i, content := range contents {
    g.Go(func() error {
      ok := filter(contents[i], pred)
      results[i] = Result{content: content, ok: ok}

      return nil
    })
  }

  g.Wait()

  var filtered []string
  for _, r := range results {
    if r.ok {
      filtered = append(filtered, r.content)
    }
  }

  return filtered
}

filterAll 函数调用 filter 函数来应用谓词到每个内容上,但谓词的应用可能会花费一些时间,可能超过上下文设置的截止时间。由于 filter 函数不使用上下文,因此它不会因为截止日期错误而失败。

我们需要重新定义 filterAll,使其使用上下文并检查其中的错误,而不管 filter 函数是否使用了上下文。

// filterAll 函数同时过滤所有内容,并检查上下文中的错误。
func filterAll(ctx context.Context, contents []string, pred func(string) bool) ([]string, error) {
  type Result struct {
    content string
    ok      bool
  }

  results := make([]Result, len(contents))

  g, ctx := errgroup.WithContext(ctx)
  for i, content := range contents {
    g.Go(func() error {
      if err := ctx.Err(); err != nil {
        return err
      }

      ok := filter(contents[i], pred)
      results[i] = Result{content: content, ok: ok}

      return nil
    })
  }

  if err := g.Wait(); err != nil {
    return nil, err
  }

  var filtered []string
  for _, r := range results {
    if r.ok {
      filtered = append(filtered, r.content)
    }
  }

  return filtered, nil
}

我们的新实现 filterAll 函数会检查上下文中的任何错误,即使上下文并未直接传递给下游函数(在本例中为 filter)。如果发生了与上下文相关的截止日期(或任何其他错误),整个过滤过程就会失败。

现在,让我们完成对所有内容的处理。

// processURLs 函数下载每个 URL 的内容并对其进行过滤。
//
// 处理必须在上下文截止日期内完成。
func processURLs(ctx context.Context, urls []string) ([]string, error) {
  contents, err := downloadAll(ctx, urls)
  if err != nil {
    return nil, err
  }

  filtered, err := filterAll(ctx, contents, somePredicate)

  return filtered, err
}

如果任何一个下载操作花费的时间过长,那么在尝试获取内容时就会发生截止日期错误,因为上下文被直接用于 API 调用。因此,downloadAll 函数也会失败,进而导致 processURLs 失败。

如果所有的 URL 在截止日期内都被正确下载,我们将继

续对它们进行过滤。在对每个下载内容进行过滤时,不使用上下文,但 filterAll 函数明确地检查上下文中的错误,如果发生了与上下文相关的截止日期(或任何其他错误),整个过滤过程就会失败。

有时候,仅仅使用 errgroup.WithContext 是不足以检测到上下文中的截止日期或其他问题的,特别是当上下文未直接使用时。因此,我们应该定期检查是否仍在时间限制内,否则就会失败。

最后,我们可以通过编写 filterAll 的测试来确保我们正确地处理了类似的情况,以确保我们尊重与上下文相关的任何错误。

func TestContextError(t *testing.T) {
  ctx, done := context.WithTimeout(context.Background(), time.Nanosecond)
  defer done()

  // 生成我们想要应用过滤器的一些数据。
  var contents []string = testingContent()

  _, err := filterAll(ctx, contents, thePredicate)
  if err == nil {
    t.Errorf("filterAll() = %v, want error", err)
  }
}

请注意,在测试中,我们期望 filterAll 会失败,因为我们设置的超时时间只有一纳秒。因此,上下文应该因为超过截止时间而发生错误。如果在启动 Goroutine 进行下载内容过滤时不检查 context.Err(),我们将永远不会处理此类错误。

到此这篇关于详解Go语言如何利用上下文进行并发计算的文章就介绍到这了,更多相关Go并发计算内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • GO语言中defer实现原理的示例详解

    GO语言中defer实现原理的示例详解

    这篇文章主要为大家详细介绍了Go语言中defer实现原理的相关资料,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,需要的可以参考一下
    2023-02-02
  • Go语言常见数据结构的实现详解

    Go语言常见数据结构的实现详解

    这篇文章主要为大家学习介绍了Go语言中的常见数据结构(channal、slice和map)的实现,文中的示例代码简洁易懂,需要的可以参考一下
    2023-07-07
  • golang中拿slice当queue和拿list当queue使用分析

    golang中拿slice当queue和拿list当queue使用分析

    这篇文章主要为大家介绍了golang 中拿slice当queue和拿list当queue使用分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 深入理解Go Gin框架中间件的实现原理

    深入理解Go Gin框架中间件的实现原理

    在Go Gin框架中,中间件是一种在请求处理过程中插入的功能模块,它可以用于处理请求的前置和后置逻辑,例如认证、日志记录、错误处理等,本文将给大家介绍一下Go Gin框架中间件的实现原理,需要的朋友可以参考下
    2023-09-09
  • Go语言实现JSON解析的神器详解

    Go语言实现JSON解析的神器详解

    php转go是大趋势,越来越多公司的php服务都在用go进行重构,重构过程中,会发现php的json解析操作是真的香。本文和大家分享了一个Go语言实现JSON解析的神器,希望对大家有所帮助
    2023-01-01
  • MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法

    MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法

    这篇文章主要介绍了MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • Go基础教程系列之import导入包(远程包)和变量初始化详解

    Go基础教程系列之import导入包(远程包)和变量初始化详解

    这篇文章主要介绍了Go基础教程系列之import导包和初始化详解,需要的朋友可以参考下
    2022-04-04
  • Go+Lua解决Redis秒杀中库存与超卖问题

    Go+Lua解决Redis秒杀中库存与超卖问题

    本文主要介绍了Go+Lua解决Redis秒杀中库存与超卖问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • Go语言中GORM存取数组/自定义类型数据

    Go语言中GORM存取数组/自定义类型数据

    在使用gorm时往往默认的数据类型不满足我们的要求,需要使用一些自定义数据类型作为字段类型,下面这篇文章主要给大家介绍了关于Go语言中GORM存取数组/自定义类型数据的相关资料,需要的朋友可以参考下
    2023-01-01
  • Go使用MongoDB的操作指南(增删改查)

    Go使用MongoDB的操作指南(增删改查)

    MongoDB 是一种高性能、开源、文档型的 NoSQL 数据库,广泛应用于 Web 应用、大数据以及云计算领域,Go 语言则以其快速、开发效率高、代码可维护性强著称,本指南将详细介绍如何在 Go 语言中使用 MongoDB 进行数据库操作,需要的朋友可以参考下
    2024-08-08

最新评论