首页 > 其他分享 >Go 实用技巧

Go 实用技巧

时间:2024-07-24 22:30:04浏览次数:6  
标签:Context 实用技巧 函数 ctx context Go payment

Golang实用技巧

Context

1. 为了gorouines更可靠,避免实用context.Background()

在我们同时管理多个任务时,我们会实用goroutine,对吧?每个goroutine专用于特定的功能,比如发HTTP请求或者查询数据库。当这些任务需要暂停时,问题就出现了,这也是棘手的地方。因为我们不希望goroutines永久停止或阻塞,没有办法退出。

“为什么我们应该避免直接使用 context.Background() ?”

我们之所以避免直接使用 context.Background(),主要是因为它无法在出现问题时停止或取消执行。它是我们能用context的最简单形式,没有值(values) 、没有截止日期(截止日期)、没有取消信号(cancellation signals)。当执行卡住或要顺利结束时,这可能会成为一个问题。

为了解决这个问题,通常我们依靠两个策略:取消(cancel)和超时(timeout):

context.WithTimeout(ctx, duration)
context.WithTimeoutCause(ctx, duration, errors.New("custom message"))

context.WithCancel(ctx)
context.WithCancelCause(ctx)

context.WithDeadline(ctx)
context.WithDeadlineCause(ctx, deadline, errors.New("custom message"))

有了这些工具,我们启动每个 goroutine 都带有明确的预期:“goroutine可以及时完成任务或者解释为什么不能及时完成,另外必要情况下可以取消任务。”

以下几点请记住:

  • WithTimeout 其实就是换了个名字的 WithDeadline。
  • 最近更新的 Go(版本 1.20 和 1.21)中添加的 XXXCause 函数提供了更好的错误报告。
  • 如果 XXXCause 发生超时,它会提供更详细的错误消息:“context deadline exceeded: custom message.

"channels怎么样?我不想在channel上永久等待。"

为了确保不会无限等待处理channels,更好的管理方法是使用 select 语句,它允许我们设置超时选项:

select{
  case result := <-ch:
    fmt.Println("Received result:", result)
  case <-time.After(5*time.Second):
    fmt.Println("Timed out")
}

不过需要注意:使用 time.After 可能会导致短期内存泄露。在某些情况下,使用time.Timer 或 time.Ticker 可能更有效,因为它们能让我们更好地控制时间。

译者补充:关于 time.After 可能导致内存泄露的文章,可参考学习:

  1. [golang]golang time.After使用不当导致内存泄露问题分析 - luoming1224 - 博客园 (cnblogs.com)
  2. Go坑:time.After可能导致的内存泄露问题分析 - 九卷 - 博客园 (cnblogs.com)

本文将在接下来技巧中深入探讨替代方案及其价值。

2. 不幸得是,context.Value 不是我们的朋友

context.Value 似乎是一个方便的工具,因为它可以在 context 中携带一些数据,然后在需要的地方取出这些数据。

这会让我们的函数签名简介明了,不是吗?典型的例子是这样的:

func A(ctx context.Context, transactionID string){
    payment := db.GetPayment(ctx, transactionID)
    ctx := context.WithValue(ctx, "payment", payment)
    
    B(ctx)
}

func B(ctx context.Context){
    ...
    
    C(ctx)
}

func C(ctx context.Context){
    payment, ok := ctx.Value("payment").(payment)
    ...
}

在这段代码中,函数 A 获取付款记录并将其添加到 context 中, 在 B 内调用的函数 C 检索此付款。这种方法避免直接通过函数 B 传递付款记录,该函数不需要了解付款。

此方法看起来不错,因为:

  • 允许我们省略函数传递特定不使用的数据,就像 B 函数一样。
  • 允许将必要的数据保存在 context 中。
  • 避免函数签名中的额外参数。

为什么不在函数中 A 中直接调用函数 C 呢?通常情况下,C与B的逻辑结合得很深,可能依赖它的某些计算和参数。

那么问题出在哪里呢?问题就出在这里:

  • 放弃了Go在编译过程中提供的类型检查安全性。
  • 我们将数据放入黑匣子中并希望以后能再找到它,而一周之后可能就像盲人搜索一样。
  • 由于隐士传递,付款数据似乎可有可无,但实际上非常重要。

从个人角度来看,使用 ctx.Value 的主要问题在于它如何隐藏数据。这就像把东西放在一个没有明确标签的保险箱里。当然,数据被保存起来了,但检索数据却成了一个猜谜游戏。

明确我们正在传递的消息通常会减少以后的麻烦。

"那么,什么时候适合使用 context.Value() ?"

最好限制它的使用范围,但 Go 文档建议在跨API和进程间传递请求范围的值时使用它。以下是一些很好的用途:

你可以考虑使用它来跟踪某些与请求相关的数据,例如:

  • 跟踪请求的开始时间
  • 记录访问者的IP地址
  • 管理追踪和跨度IDs
  • 识别正在访问的HTTP路由
  • ....

上面例子中 'payment' 支付数据与请求无关吗?

如果 ‘payment’ 支付信息在多个函数中都很重要,那么在函数参数显示传递它会更清晰、安全,并且有助于任何阅读代码的人立即理解该函数直接与‘payment’支付数据交互。

一般来说,最好避免在 context 中嵌入关键业务数据,这种策略可以保持代码清晰度和可维护性。

3. 使用context.WithoutCancel 保持 context 活跃

当在Go中实用context时,非常简单的一件就是直接使用带有取消功能的context。如果取消父级context,所有子context也会被取消。

比如,下面这个简单的例子:

parentCtx, cancel := context.WithCancel(context.Background())
childCtx, _ := context.WithCancel(parentCtx)

go func(ctx context.Context){
    <-ctx.Done()
    fmt.Println("Child context done")
}(childCtx)

cancel()

此段代码中,一旦我们取消 parentCtx, childCtx 也会被取消。这通常是我们想要的,但有时我们可能需要一个子context能够继续运行,即使父级context被取消了。

在Go中处理HTTP请求时,我们会经常遇到一个场景:在处理主请求后启动goroutine处理任务,如果处理不仔细可能会导致错误:

func handleRequest(req *http.Request){
    ctx := req.Context()
    
    go hookAfterRequest(ctx)
}

在我们考虑处理HTTP请求时,即使客户端断开连接,我们仍然需要记录详细信息并收集指标,而不是取消运行。

未完待翻译,感兴趣可以先看原仓库英文版。喜欢给个鼓励 star !
中文:Go实用技巧
英文:go-practical-tips

标签:Context,实用技巧,函数,ctx,context,Go,payment
From: https://www.cnblogs.com/Lusai/p/18321912

相关文章

  • Django 表单常用字段参数
    DjangoForm表单,常用表单字段-CSDN博客        在Django中,表单(Form)是用来处理HTML表单数据的重要工具。Django的表单API允许你定义表单字段及其验证规则。每个表单字段都可以通过多种参数来定制其行为。以下是一些常用的表单字段参数:label:字段的标签,用于在HTML表单......
  • c++(4) pangolin可视化多线程
     CMakeLists.txtcmake_minimum_required(VERSION2.8)project(3DTo3D)set(CMAKE_CXX_STANDARD11)set(CMAKE_BUILD_TYPERelease)find_package(PangolinREQUIRED)#可视化工具库include_directories(${Pangolin_INCLUDE_DIRS})#OpenCV#find_package(OpenCV......
  • 如何使用 Google Drive API 识别和删除大型 Google Takeout ZIP 文件?
    如何使用GoogleDriveAPI识别和删除大型GoogleTakeoutZIP文件?正文:我遇到一个问题:GoogleTakeout不断在我的Google云端硬盘中创建大型ZIP文件,导致其达到存储限制。我需要使用GoogleDriveAPI以编程方式识别和删除这些文件。我注意到文件名遵循类似takeou......
  • golang并发编程(新手向)
    golang并发所有工具的简单介绍1goroutine:准确来说这并不是一个和C#Java一样的线程,而是golangruntime管理的一个轻量级线程,但是我们完全可以把他当做是一个线程,使用go关键字来开启2channel:这是一种通信方式,相当于不同线程之间建立了管道通信的机制,管道有很多种,但是只要把......
  • PyCharm 中的 ndb Google Cloud Datastore 的类型提示丢失
    ndbModel类的实例对象没有类型提示。例如some_datetime不显示任何类型提示。类型提示在其他地方起作用。我是否需要安装专门用于类型提示的东西才能与ndb库一起使用?fromgoogle.cloudimportndbclassMyModel(ndb.Model):some_datet......
  • 从 IFRAME javascript 到 google colab 的回调函数
    所以我在学习googlecolab时遇到了一个问题,在googlecolab中运行我的代码,我打开服务器并使用IFRAME查看我的网站,我试图解决的问题是选择json文件并单击上传时我希望该文件上传到我的笔记本本地内存,我的index.html文件有一个回调函数:<script>functionuploadJs......
  • Golang格式化占位符详解
    Golang格式化占位符详解在Golang编程中,格式化字符串是一种常见的操作,它可以用来将不同类型的数据格式化为字符串输出。在格式化字符串时,占位符起着至关重要的作用,通过占位符可以指定数据的输出格式。本文将详细介绍Golang中一些常用的格式化占位符,并提供具体的代码示例供大......
  • Django REST框架serializer.is_valid()将文件保存到MEDIA_ROOT,尽管.is_valid()为False
    我有一个支持通过Django表单和DjangoREST框架上传文件的项目,这些文件用于存储在模型“文档”中。问题是,当通过REST框架上传验证失败的文件时,该文件仍保存到其“upload_to”参数(MEDIA_ROOT)(未按预期创建“文档”模型实例),上传时不会发生这种情况一些测试似乎表明“ser......
  • golang channel 和 mutex 及原子操作 用于并发控制的性能对比
    场景:对同个数加10w次,看耗费时间,这里没有用benchmark测试,在意的请略过。以下是测试代码:packagemainimport( "fmt" "sync" "sync/atomic" "testing" "time")funcTestCount(t*testing.T){ varcntint varwgsync.WaitGroup num......
  • 服务器部署环境(docker安装Mysql + Redis + MongoDB)
    1.安装Docker1、选择要安装的平台Docker要求CentOS系统的内核版本高于3.10uname-r#通过uname-r命令查看你当前的内核版本官网地址2.卸载已安装的Docker,使用Root权限登录Centos。确保yum包更新到最新。sudoyumupdate如果操作系统没有安装过Dock......