Go中如何使用context实现并发任务控制

在Go语言中进行并发编程时,我们通常需要协同处理多个任务,其中有些任务可能需要在特定条件下停止或者取消执行。这时候,context包就能够帮我们实现控制并发执行的任务。

1. 理解Context

在介绍如何在Go中使用context包实现并发任务控制前,我们先来了解一下它的基本概念。

一般来说,context是程序执行的上下文环境,包括了一些元数据、系统环境和运行状态等信息。在Go语言中,context包提供了一种方式,使得我们能够在同一个goroutine或者跨goroutine间传递请求作用域中的变量。

context包中最常用的两个类型分别是Context和CancelFunc。前者是实际的上下文环境,而后者是一个取消函数,能够在需要停止某个任务时,向Context发送信号,让它自动终止运行。

2. Context的使用方法

2.1 Context的创建

我们可以使用context包中的WithCancel、WithDeadline、WithTimeout和WithValue等函数创建Context。其中,WithValue函数可以与前三个函数搭配使用,实现跨goroutine传递数据。

2.2 Context的取消

当Context的Done方法返回值为一个已关闭的channel时,表示Context已被取消。此时,我们需要通过Context的Err方法来获取取消原因。同时,我们也可以通过Context的Done方法阻塞,等待Context的取消信号。

3. 在并发任务中使用Context

下面,我们将通过利用Context,来实现一个并发执行多个任务的示例。

假设我们现在需要开发一个服务,用于向多个远程API提交请求,并将它们合并为一个响应。对于每个请求,我们为其封装了一个独立的goroutine:

type Request struct {

Method string

URL string

Payload []byte

}

type Response struct {

StatusCode int

Body []byte

Err error

}

func doRequest(ctx context.Context, req Request) (*Response, error) {

httpReq, err := http.NewRequest(req.Method, req.URL, bytes.NewBuffer(req.Payload))

if err != nil {

return nil, err

}

httpReq.Header.Set("Content-Type", "application/json")

ctxReq := httpReq.WithContext(ctx)

resp, err := http.DefaultClient.Do(ctxReq)

if err != nil {

return nil, err

}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {

return nil, err

}

return &Response{

StatusCode: resp.StatusCode,

Body: body,

Err: nil,

}, nil

}

在这个示例中,我们使用了http.Do函数来执行HTTP请求,同时传入了一个带有Context的httpReq。如果任何一个请求的Context被取消,http.Do函数会自动取消相应的请求。

下面是我们如何使用Context控制并发任务的代码:

ctx := context.Background()

ctx, cancel := context.WithCancel(ctx)

defer cancel()

requests := []Request{

{

Method: "GET",

URL: "https://jsonplaceholder.typicode.com/todos/1",

Payload: nil,

},

{

Method: "GET",

URL: "https://jsonplaceholder.typicode.com/todos/2",

Payload: nil,

},

}

responses := make(chan *Response, len(requests))

for _, r := range requests {

r := r

go func() {

resp, err := doRequest(ctx, r)

if err != nil {

resp = &Response{Err: err}

}

responses <- resp

}()

}

for i := 0; i < len(requests); i++ {

select {

case <-ctx.Done():

fmt.Println("Context cancelled!")

return

case resp := <-responses:

fmt.Printf("Response received: %d\n", resp)

}

}

在这个示例中,我们首先创建了一个带有CancelFunc的Context,然后使用一个goroutine为每个请求启动一个执行线程。在处理每个请求响应时,我们先判断是否有错误,如果没有,就将响应保存在一个responses通道中。在主goroutine中,我们使用select语句等待所有请求完成或者某个Context被取消。

最后,如果我们在20秒钟内未收到所有响应,我们可以使用第二个CancelFunc来通知所有正在执行的goroutine,并让它们自动取消。代码如下:

cancel2 := func() {

for range requests {

cancel()

}

}

time.AfterFunc(20*time.Second, cancel2)

4. 总结

通过这篇文章,我们已经学习了如何在Go语言中使用Context来控制并发任务的执行。我们经常会遇到一些远程请求、数据库查询等复杂操作,这些操作带来的不确定性和异常,可能会破坏整个系统的可用性和稳定性。使用Context,可以让我们更方便地控制这些操作的执行,让我们的程序更加健壮。

后端开发标签