Golang 中 Goroutines 和 Channels 的超时和取消机制

1. Goroutines 和 Channels 简介

Goroutines 是 Golang 中一种轻量级的线程实现。与传统线程相比,创建和销毁 Goroutines 所花费的时间更少,使得并发编程变得更加容易。Channels 则是 Goroutines 之间的通信机制,其类似于 Unix 中的文件描述符,可以用来进行进程通信。

func worker(id int, jobs <-chan int, results chan<- int) {

for j := range jobs {

fmt.Println("worker", id, "processing job", j)

time.Sleep(time.Second)

results <- j * 2

}

}

func main() {

jobs := make(chan int, 100)

results := make(chan int, 100)

// 创建3个工作者

for w := 1; w <= 3; w++ {

go worker(w, jobs, results)

}

// 提交9个工作

for j := 1; j <= 9; j++ {

jobs <- j

}

close(jobs)

// 输出结果

for a := 1; a <= 9; a++ {

<-results

}

}

2. Goroutines 的超时机制

2.1 使用 time.After()

在 Golang 中,Goroutines 的超时机制可以使用 time 包中提供的 After() 函数来实现。After() 函数会返回一个 channel,一旦指定的时间到达就会向 channel 中发送一个消息。

select {

case <-time.After(2 * time.Second):

fmt.Println("请求超时")

case res := <-myChannel:

fmt.Println(res)

}

在上面的示例中,select 会监听来自两个 channel 的消息,一旦超过2秒钟还没有从 myChannel 中收到消息,就会输出"请求超时"。否则,如果 myChannel 收到消息,就会输出消息的内容。

2.2 使用 context 包实现超时

为了更好地实现 Goroutines 的超时机制,Golang 还提供了 context 包。使用 context 包的 WithTimeout() 函数,可以将超时机制自动与 Goroutines 关联起来。

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)

defer cancel()

req, err := http.NewRequest("GET", "https://example.com", nil)

if err != nil {

log.Fatalf("发起请求失败:%v\n", err)

}

req = req.WithContext(ctx)

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

if err != nil {

log.Fatalf("请求失败:%v\n", err)

}

defer resp.Body.Close()

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

if err != nil {

log.Fatalf("读取响应体失败:%v\n", err)

}

fmt.Printf("响应体:%s\n", body)

上面的示例中,我们向 example.com 发起了一次 GET 请求,并设置了一个超时时间为2秒钟。如果在2秒钟内没有收到响应,就会触发 context 超时,程序会自动取消该请求。

3. Goroutines 的取消机制

3.1 使用 context 包实现取消

在 Golang 中,可以使用 context 包的 WithCancel() 函数来创建一个上下文 (context),该上下文可以用来取消 Goroutines 的运行。

ctx, cancel := context.WithCancel(context.Background())

go func() {

select {

case <-ctx.Done():

fmt.Println("收到取消信号")

case <-time.After(5 * time.Second):

fmt.Println("完成任务")

}

}()

time.Sleep(2 * time.Second)

cancel()

上面的示例中,我们首先使用 context 包的 WithCancel() 函数创建了一个上下文 (ctx) 和对应的取消函数 (cancel)。然后,我们启动了一个新的 Goroutine,在其中监听 ctx.Done() 信号,一旦收到该信号就会输出"收到取消信号"。同时,在该 Goroutine 中我们还监听了一个 5 秒钟的定时器,一旦定时器时间到达就会输出"完成任务"。

在主 Goroutine 中,我们调用了 cancel() 函数,用于向 ctx 发送一个取消信号。一旦 ctx 收到了取消信号,该 Goroutine 就会优雅地退出。

3.2 使用 close() 函数实现取消

Golang 中,关闭一个 channel 会导致所有从该 channel 接收数据的 Goroutines 获得一个零值和 false 的标志,这意味着,可以使用 close() 函数来关闭一个 channel 从而通过 Goroutines 的并发执行情况完成取消的功能。

quit := make(chan bool)

go func() {

for {

select {

case <-quit:

fmt.Println("任务取消")

return

default:

fmt.Println("执行任务中……")

time.Sleep(1 * time.Second)

}

}

}()

time.Sleep(3 * time.Second)

fmt.Println("取消任务")

close(quit)

time.Sleep(1 * time.Second)

fmt.Println("退出程序")

上面的示例中,我们启动了一个 Goroutine,在其中不断输出"执行任务中……"。我们使用 select 来监听来自 quit channel 的信号,一旦收到了信号就输出"任务取消"并退出 Goroutine。

在主 Goroutine 中,我们等待3秒钟然后输出"取消任务",接着关闭 quit channel,从而使得监视 quit channel 的 Goroutine 收到一个零值和 false 的标志,从而实现了任务的取消功能。

4. 总结

本文介绍了 Golang 中 Goroutines 和 Channels 的超时和取消机制。超时机制可以借助 time 包和 context 包实现,而取消机制可以借助 context 包中的取消函数或者 close() 函数来实现。在使用这些机制时,需要注意程序的阻塞情况,以免造成资源的浪费。

后端开发标签