Go十大常见错误第8篇:并发编程中Context使用常见错误
Posted coding进阶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go十大常见错误第8篇:并发编程中Context使用常见错误相关的知识,希望对你有一定的参考价值。
前言
这是Go十大常见错误系列的第8篇:并发编程中Context使用常见错误。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。
本文涉及的源代码全部开源在:Go十大常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。
Context是什么
Go语言标准库里有一个package叫context
,该package里定义了context.Context类型,在并发编程里非常有用,但是也经常被开发者误解。
官方对Context的表述是:
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
光看这段描述,还是很容易让人迷糊的,我们接下来具体看看Context到底是什么以及可以帮助我们做什么事情。
Context顾名思义,表示的是goroutine的上下文,Context定义如下所示:
// A Context carries a deadline, cancellation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface
// Done returns a channel that is closed when this Context is canceled
// or times out.
Done() <-chan struct
// Err indicates why this context was canceled, after the Done channel
// is closed.
Err() error
// Deadline returns the time when this Context will be canceled, if any.
Deadline() (deadline time.Time, ok bool)
// Value returns the value associated with key or nil if none.
Value(key interface) interface
Context可以通过超时设置、携带取消信号、附加参数信息来方便goroutine里做相应的逻辑控制。
-
超时控制。 通过
context.WithTimeout
函数和context.WithDeadline
函数可以创建一个有超时时间的Context。通过Context的Done
函数可以判断是否超时了。func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
-
取消信号。通过
context.WithCancel
函数可以创建一个接收cancel信号的Context。通过Context的Done
函数可以判断是否发出了cancel信号。父Context发出的cancel信号,子Context也可以接收到。func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
-
附加参数信息。通过
context.WithValue
函数可以给Context添加参数。其中key和value都是空接口类型(interface
)。通过Context的Value
函数可以获取附加参数信息。func WithValue(parent Context, key, val any) Context
在实际开发过程中,Context的使用流程一般是:
- Step 1: 创建Context,给Context指定超时时间,设置取消信号,或者附加参数(链路跟踪里经常使用Context里的附加参数,传递IP等链路跟踪信息)。
- Step 2: goroutine使用Step 1里的Context作为第一个参数,在该goroutine里就可以做如下事情:
- 使用Context里的
Done
函数判断是否达到了Context设置的超时时间或者Context是否被主动取消了。 - 使用Context里的
Value
函数获取该Context里的附加参数信息。 - 使用Context里的
Err
函数获取错误原因,目前原因就2个,要么是超时,要么是主动取消。
- 使用Context里的
有2点要补充:
- 第一,Context是可以组合的。比如,我们可以通过
context.WithTimeout
创建一个有超时时间的Context,再调用context.WithValue
添加一些附加参数信息。 - 第二,多个goroutine可以共享同一个Context,可以通过该Context的超时设置、携带取消信号以及附加参数来控制多个goroutine的行为。
常见错误
在Context使用过程中有以下几个常见错误:
-
第一,不执行
cancel
函数去释放Context资源。-
对于
context.WithTimeout
、context.WithDeadline
、context.WithCancel
函数返回的cancel函数,需要做执行。官方说明如下:Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete:
参考代码示例:
func slowOperationWithTimeout(ctx context.Context) (Result, error) ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() // releases resources if slowOperation completes before timeout elapses return slowOperation(ctx)
-
-
第二,不加超时控制,如果执行了非常耗时的rpc操作或者数据库操作,就会阻塞程序。如果rpc调用接口或者数据库操作接口支持传递Context参数,建议加上超时设置。代码示例参考如下:
ctx, cancel := context.WithTimeout(parent, 100 * time.Millisecond) response, err := grpcClient.Send(ctx, request)
推荐阅读
开源地址
文章和示例代码开源在GitHub: Go语言初级、中级和高级教程。
公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。
个人网站:Jincheng’s Blog。
知乎:无忌。
福利
我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。还可以发送消息「进群」,和同行一起交流学习,答疑解惑。
References
- 参考文章:https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65
- 官方文档:https://pkg.go.dev/context
- 官方Context入门介绍:https://go.dev/blog/context
- Context使用介绍:https://mp.weixin.qq.com/s/PoXSEDHRyKCyjibFGS0wHw
- https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go
本文由mdnice多平台发布
Go十大常见错误第9篇:使用文件名称作为函数输入
前言
这是Go十大常见错误系列的第9篇:使用文件名称作为函数输入。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。
本文涉及的源代码全部开源在:Go十大常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。
问题场景
一个常见错误是把文件名作为函数参数,在函数里读取文件内容。
比如,我们要实现一个函数,用来统计指定文件里有多少空行,很多人最容易联想到的实现方式如下:
func count(filename string) (int, error)
file, err := os.Open(filename)
if err != nil
return 0, errors.Wrapf(err, "unable to open %s", filename)
defer file.Close()
scanner := bufio.NewScanner(file)
count := 0
for scanner.Scan()
if scanner.Text() == ""
count++
return count, nil
这段代码逻辑很简单:
- 文件名作为函数入参
- 函数里读取文件每一行数据,判断是否为空行,如果是空行就计数+1
这种方式其实也没大问题,比较好理解。只是可扩展性不强,没有充分利用到Go语言里关于数据读写的接口(interface)类型的优势。
试想下,如果你想对一个HTTP body里的内容实现相同的逻辑,那上面的代码无法支持,要另外实现一个新的函数。
解决方案
Go语言里有2个很好的抽象接口(interface),分别是io.Reader
和io.Writer
。
和上面函数传参使用文件名不一样,我们可以使用io.Reader
作为函数的参数类型。
因为文件、HTTP body、bytes.Buffer都实现了io.Reader
,所以
- 使用
io.Reader
作为函数参数可以兼容不同类型的数据源。 - 不同的数据源,可以统一使用
io.Reader
类型里的Read
方法来读取数据。
具体到这个例子里,我们可以使用bufio.Reader
和其ReadLine
方法,代码如下所示:
func count(reader *bufio.Reader) (int, error)
count := 0
for
line, _, err := reader.ReadLine()
if err != nil
switch err
default:
return 0, errors.Wrapf(err, "unable to read")
case io.EOF:
return count, nil
if len(line) == 0
count++
err为io.EOF
,就表示读到了空行。
EOF is the error returned by Read when no more input is available. (Read must return EOF itself, not an error wrapping EOF, because callers will test for EOF using ==.) Functions should return EOF only to signal a graceful end of input. If the EOF occurs unexpectedly in a structured data stream, the appropriate error is either ErrUnexpectedEOF or some other error giving more detail.
有了上面的count
函数,我们就可以使用如下的方式打开文件,计算文件里空行的数量。
file, err := os.Open(filename)
if err != nil
return errors.Wrapf(err, "unable to open %s", filename)
defer file.Close()
count, err := count(bufio.NewReader(file))
这种实现方式可以让我们在计算逻辑里不需要关心真正的数据来源。同时,也可以方便我们做单元测试。
比如下面的例子,我们直接把字符串作为输入,来测试上面实现的count
函数。
count, err := count(bufio.NewReader(strings.NewReader("input")))
推荐阅读
开源地址
文章和示例代码开源在GitHub: Go语言初级、中级和高级教程。
公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。
个人网站:Jincheng’s Blog。
知乎:无忌。
福利
我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。还可以发送消息「进群」,和同行一起交流学习,答疑解惑。
References
- https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65
以上是关于Go十大常见错误第8篇:并发编程中Context使用常见错误的主要内容,如果未能解决你的问题,请参考以下文章