面试题答案
一键面试设计思路
- 创建 context:使用
context.WithCancel
创建一个可取消的context
,这个context
会在所有相关 goroutine 之间传递。 - 传递 context:将创建的
context
传递给每个执行独立操作(数据库查询、网络请求、本地文件操作)的 goroutine。 - 监听取消信号:在每个 goroutine 中,通过
ctx.Done()
通道监听取消信号。一旦接收到取消信号,立即停止当前操作并进行资源清理。 - 错误处理与取消:当任意一个操作出现错误时,调用
cancel
函数取消context
,从而通知其他 goroutine 停止。
关键代码片段
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"time"
)
// 模拟数据库查询
func dbQuery(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 模拟数据库查询操作
time.Sleep(2 * time.Second)
return nil
}
}
// 模拟网络请求
func networkRequest(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
req, err := http.NewRequestWithContext(ctx, "GET", "http://example.com", nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
}
// 模拟本地文件操作
func localFileOperation(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 模拟本地文件操作
_, err := ioutil.ReadFile("example.txt")
return err
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var errs []error
// 启动数据库查询 goroutine
go func() {
if err := dbQuery(ctx); err != nil {
cancel()
errs = append(errs, err)
}
}()
// 启动网络请求 goroutine
go func() {
if err := networkRequest(ctx); err != nil {
cancel()
errs = append(errs, err)
}
}()
// 启动本地文件操作 goroutine
go func() {
if err := localFileOperation(ctx); err != nil {
cancel()
errs = append(errs, err)
}
}()
// 等待一段时间,确保所有 goroutine 有机会执行
time.Sleep(5 * time.Second)
if len(errs) > 0 {
for _, err := range errs {
fmt.Println("Error:", err)
}
} else {
fmt.Println("All operations completed successfully")
}
}
在上述代码中:
- 每个操作函数(
dbQuery
、networkRequest
、localFileOperation
)都通过ctx.Done()
监听取消信号。 - 当任意一个操作出现错误时,调用
cancel()
取消context
,从而通知其他 goroutine 停止。 main
函数中启动了三个独立的 goroutine 分别执行不同的操作,并等待一段时间后检查是否有错误发生。