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个,要么是超时,要么是主动取消。

有2点要补充:

  • 第一,Context是可以组合的。比如,我们可以通过context.WithTimeout创建一个有超时时间的Context,再调用context.WithValue添加一些附加参数信息。
  • 第二,多个goroutine可以共享同一个Context,可以通过该Context的超时设置、携带取消信号以及附加参数来控制多个goroutine的行为。

常见错误

在Context使用过程中有以下几个常见错误:

  • 第一,不执行cancel函数去释放Context资源。

    • 对于context.WithTimeoutcontext.WithDeadlinecontext.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.Readerio.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使用常见错误的主要内容,如果未能解决你的问题,请参考以下文章

2.1 Go微服务实战(Go语言进阶) --- 并发编程进阶

Go开发中的十大常见陷阱[译]

Go 系列教程 ——第 25 篇:Mutex

067-Go 并发编程

GO并发编程的数据竞争问题

GO并发编程方面的一些常见面试问题