面试题答案
一键面试场景描述
假设我们有一个Goroutine用于执行一个长时间运行的任务,例如从网络下载一个大文件。在下载过程中,如果用户突然取消下载操作,就需要在外部主动结束这个正在执行下载任务的Goroutine。
通过channel实现主动结束
package main
import (
"fmt"
"time"
)
func main() {
stopCh := make(chan struct{})
go func() {
for {
select {
case <-stopCh:
fmt.Println("Goroutine stopped")
return
default:
fmt.Println("Doing some work...")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(3 * time.Second)
close(stopCh)
time.Sleep(1 * time.Second)
}
在上述代码中,我们创建了一个无缓冲的stopCh
通道。Goroutine通过select
语句监听这个通道,当通道接收到信号(close(stopCh)
)时,Goroutine就会结束。
通过context实现主动结束
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine stopped")
return
default:
fmt.Println("Doing some work...")
time.Sleep(1 * time.Second)
}
}
}(ctx)
time.Sleep(5 * time.Second)
}
这里使用context.WithTimeout
创建了一个带有超时的上下文ctx
,cancel
函数用于主动取消上下文。Goroutine通过监听ctx.Done()
通道来判断是否需要结束。
context的作用及原理
- 作用:
context
主要用于在多个Goroutine之间传递请求范围的截止时间、取消信号以及其他请求相关的值。它为Goroutine提供了一种标准的方式来进行协作式取消和超时控制,使得资源能够得到合理的管理,避免资源泄露。 - 原理:
context
是一个接口类型,它的实现包含了取消功能、截止时间以及携带的值。context
可以被传递给不同的Goroutine,当父context
被取消或超时,所有从它衍生出来的子context
也会收到相应的取消信号。ctx.Done()
返回的通道会在context
被取消或超时时关闭,Goroutine通过监听这个通道来做出相应的处理,从而实现资源的合理释放和任务的有序结束。