🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁
🦄 博客首页——🐅🐾猫头虎的博客🎐
🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺
🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐
🌊 《100天精通Golang(基础入门篇)》 🐅 学会Golang语言,畅玩云原生,走遍大小厂~💐
🐅🐾猫头虎建议Go程序员必备技术栈一览表📖:
☁️🐳
Go语言开发者必备技术栈☸️
:
🐹 GoLang | 🌿 Git | 🐳 Docker | ☸️ Kubernetes | 🔧 CI/CD | ✅ Testing | 💾 SQL/NoSQL | 📡 gRPC | ☁️ Cloud | 📊 Prometheus | 📚 ELK Stack
🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🐅🐾🍁🐥
文章目录
- 🐅🐾猫头虎建议Go程序员必备技术栈一览表📖:
- 2021年2月24日 Go生态洞察:Contexts和Structs的深度解析 🌍
- 摘要
- 引言
- 正文内容
- 优先将contexts作为参数传递 📌
- 在结构体中存储context会导致混淆 🚫
- 规则的例外:保持向后兼容性
- 总结
- 原创声明
-
原创作者: 猫头虎
-
作者wx: Libin9iOak
-
作者公众号: 猫头虎技术团队
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方法:
// 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时,问题在于你将生命周期对调用者隐藏起来,或者更糟糕的是,以不可预测的方式将两个作用域混合在一起:
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
,那么构造函数是否正在执行需要取消或有截止日期的工作? - 传递给
New
的context.Context
是否适用于(*Worker).Fetch
和(*Worker).Process
中的工作?都不是?一个而不是另一个?
API需要大量文档明确告诉用户context.Context
的确切用途。用户可能还需要阅读代码,而不是依赖于API结构所传达的内容。
最后,设计一个每个请求都没有context、因此无法充分尊重取消请求的生产级服务器可能相当危险。如果没有设置每次调用的截止日期,你的进程可能会积压并耗尽其资源(如内存)!
规则的例外:保持向后兼容性
当Go 1.7(引入了context.Context)发布时,大量API不得不以向后兼容的方式添加context支持。例如,net/http
的Client
方法,如Get
和Do
,是context的理想候选者。使用这些方法发送的每个外部请求都将受益于随context.Context
而来的截止日期、取消和元数据支持。
为了以向后兼容的方式支持context.Context
,有两种方法:在结构体中包含context(如我们马上会看到的),以及复制函数,其中复制的函数接受context.Context
并在其函数名称后缀中带有Context
。应优先选择复制函数方法而不是context-in-struct方法,这在Keeping your modules compatible中有进一步讨论。然而,在某些情况下,这可能是不切实际的:例如,如果你的API暴露了大量函数,那么全部复制它们可能是不可行的。
net/http
包选择了context-in-struct方法,这提供了一个有用的案例研究。让我们看看net/http
的Do
。在引入context.Context
之前,Do
的定义如下:
// Do发送一个HTTP请求并返回一个HTTP响应[...]
func (c *Client) Do(req *Request) (*Response, error)
在Go 1.7之后,如果不是为了保持向后兼容性,Do
可能看起来如下所示:
// Do发送一个HTTP请求并返回一个HTTP响应[...]
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)
但是,保持标准库的向后兼容性并遵守Go 1兼容性承诺至关重要。因此,维护者选择在http.Request
结构体中添加context.Context
,以支持context.Context
而不破坏向后兼容性:
// 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生态洞察专栏收录,详情点击这里。
下一篇预告:
下次我们将探讨2020年Go开发者调查的结果,深入了解Go社区的趋势和见解!🚀📊
原创声明
======= ·
-
原创作者: 猫头虎
-
作者wx: Libin9iOak
-
作者公众号: 猫头虎技术团队
学习 | 复习 | Go生态 |
---|---|---|
✔ | ✔ | ✔ |
本文为原创文章,版权归作者所有。未经许可,禁止转载、复制或引用。
作者保证信息真实可靠,但不对准确性和完整性承担责任。
未经许可,禁止商业用途。
如有疑问或建议,请联系作者。
感谢您的支持与尊重。
点击
下方名片
,加入IT技术核心学习团队。一起探索科技的未来,洞察Go生态,共同成长。