前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2021年2月24日 Go生态洞察:Contexts和Structs的深度解析

2021年2月24日 Go生态洞察:Contexts和Structs的深度解析

作者头像
猫头虎
发布2024-04-09 15:37:54
720
发布2024-04-09 15:37:54
举报

2021年2月24日 Go生态洞察:Contexts和Structs的深度解析 🌍

摘要

🐆 猫头虎博主在此!今天我们要深入探讨Go语言中的一个重要话题:Contexts和Structs。这篇文章将深入探讨context.Context的正确使用方法,特别是在API设计中如何合理地运用。对于那些在互联网深处搜索“Go语言最佳实践”、“Contexts使用指南”或者“高效API设计”等词条的开发者们,这篇文章将是你的福音!

引言

在许多现代Go API中,函数和方法的第一个参数经常是context.Context。Context提供了一种在API边界和进程间传递截止日期、调用者取消以及其他请求范围值的手段。当库直接或间接地与远程服务器(如数据库、API等)交互时,通常会使用它。

根据Context的官方文档,建议不要在结构体类型中存储Context,而应将其传递给每个需要它的函数。本文将详细解释这一建议的原因,并提供例子说明为什么将Context作为参数传递比存储在其他类型中更为重要。

正文内容

优先将contexts作为参数传递 📌

要理解为什么不在结构体中存储context,让我们考虑首选的context-as-argument方法:

代码语言:javascript
复制
// Worker从远程作业编排服务器获取并添加作业。
type Worker struct { /* … */ }

type Work struct { /* … */ }

func New() *Worker {
  return &Worker{}
}

func (w *Worker) Fetch(ctx context.Context) (*Work, error) {
  _ = ctx // A per-call ctx is used for cancellation, deadlines, and metadata.
}

func (w *Worker) Process(ctx context.Context, work *Work) error {
  _ = ctx // A per-call ctx is used for cancellation, deadlines, and metadata.
}

这里,(*Worker).Fetch(*Worker).Process方法都直接接受一个context。通过这种传递参数的设计,用户可以设置每次调用的截止日期、取消和元数据。并且,传递给每个方法的context.Context的用途非常清晰:不期望一个方法中使用的context.Context会被其他方法使用。这是因为context的范围尽可能地缩小到所需的操作,这极大地提高了该包中context的实用性和清晰度。

在结构体中存储context会导致混淆 🚫

再次检查上面的Worker示例,但这次使用不推荐的context-in-struct方法。当你在结构体中存储context时,问题在于你将生命周期对调用者隐藏起来,或者更糟糕的是,以不可预测的方式将两个作用域混合在一起:

代码语言:javascript
复制
type Worker struct {
  ctx context.Context
}

func New(ctx context.Context) *Worker {
  return &Worker{ctx: ctx}
}

func (w *Worker) Fetch() (*Work, error) {
  _ = w.ctx // A shared w.ctx is used for cancellation, deadlines, and metadata.
}

func (w *Worker) Process(work *Work) error {
  _ = w.ctx // A shared w.ctx is used for cancellation, deadlines, and metadata.
}

(*Worker).Fetch(*Worker).Process方法都使用存储在Worker中的context。这阻止了Fetch和Process的调用者(可能本身具有不同的contexts)为每次调用指定截止日期、请求取消和附加元数据。例如:用户无法仅为(*Worker).Fetch设置截止日期,或仅取消`(*Worker

).Process调用。调用者的生命周期与共享的context交织在一起,而context的范围限定在创建Worker`的生命周期内。

与传递参数方法相比,这种API对用户来说也更加令人困惑。用户可能会问自己:

  • 既然New接受一个context.Context,那么构造函数是否正在执行需要取消或有截止日期的工作?
  • 传递给Newcontext.Context是否适用于(*Worker).Fetch(*Worker).Process中的工作?都不是?一个而不是另一个?

API需要大量文档明确告诉用户context.Context的确切用途。用户可能还需要阅读代码,而不是依赖于API结构所传达的内容。

最后,设计一个每个请求都没有context、因此无法充分尊重取消请求的生产级服务器可能相当危险。如果没有设置每次调用的截止日期,你的进程可能会积压并耗尽其资源(如内存)!

规则的例外:保持向后兼容性

当Go 1.7(引入了context.Context)发布时,大量API不得不以向后兼容的方式添加context支持。例如,net/httpClient方法,如GetDo,是context的理想候选者。使用这些方法发送的每个外部请求都将受益于随context.Context而来的截止日期、取消和元数据支持。

为了以向后兼容的方式支持context.Context,有两种方法:在结构体中包含context(如我们马上会看到的),以及复制函数,其中复制的函数接受context.Context并在其函数名称后缀中带有Context。应优先选择复制函数方法而不是context-in-struct方法,这在Keeping your modules compatible中有进一步讨论。然而,在某些情况下,这可能是不切实际的:例如,如果你的API暴露了大量函数,那么全部复制它们可能是不可行的。

net/http包选择了context-in-struct方法,这提供了一个有用的案例研究。让我们看看net/httpDo。在引入context.Context之前,Do的定义如下:

代码语言:javascript
复制
// Do发送一个HTTP请求并返回一个HTTP响应[...]
func (c *Client) Do(req *Request) (*Response, error)

在Go 1.7之后,如果不是为了保持向后兼容性,Do可能看起来如下所示:

代码语言:javascript
复制
// Do发送一个HTTP请求并返回一个HTTP响应[...]
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)

但是,保持标准库的向后兼容性并遵守Go 1兼容性承诺至关重要。因此,维护者选择在http.Request结构体中添加context.Context,以支持context.Context而不破坏向后兼容性:

代码语言:javascript
复制
// Request代表一个由服务器接收或客户端发送的HTTP请求。
// ...
type Request struct {
  ctx context.Context

  // ...
}

// NewRequestWithContext返回一个新的Request,给定方法、URL和可选的
// body。
// [...]
// 给定的ctx用于Request的生命周期。
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
  // 为了本文的简洁性进行了简化。
  return &Request{
    ctx: ctx,
    // ...
  }
}

// Do发送一个HTTP

总结

使用context时,我们应该将其作为方法的第一个参数传递,而不是存储在struct类型中。这样,用户

可以充分利用它的扩展性,通过调用栈构建一个强大的取消、截止和元数据信息树。并且,当它作为参数传入时,它的作用域是清晰可见的,这导致了整个栈的清晰理解和可调试性。

知识要点总结表格:

关键点

描述

Context作为参数

提高了可读性和灵活性

避免在Structs中存储Context

防止生命周期和作用域混淆

向后兼容性

在必要时,可以在struct中添加Context

本文被猫头虎的Go生态洞察专栏收录,详情点击这里

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2021年2月24日 Go生态洞察:Contexts和Structs的深度解析 🌍
    • 摘要
      • 引言
        • 正文内容
          • 优先将contexts作为参数传递 📌
          • 在结构体中存储context会导致混淆 🚫
          • 规则的例外:保持向后兼容性
        • 总结
        相关产品与服务
        对象存储
        对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档