面试题答案
一键面试设计思路
- Context 选择:使用
context.Context
作为控制协程生命周期的主要手段。context.WithCancel
函数创建一个可取消的上下文,当需要取消任务链时,调用取消函数,该上下文及其派生的上下文都会被取消。 - 传递 Context:在启动每个协程时,将
context.Context
作为参数传递进去。这样,一旦父上下文被取消,子协程能够感知并及时停止执行。 - 资源清理:每个协程在结束时负责清理自己所占用的资源。例如,关闭文件句柄、数据库连接等。
关键代码示例
package main
import (
"context"
"fmt"
"time"
)
// 模拟一个任务函数,接受 context 和输入数据
func task(ctx context.Context, input int) (int, error) {
// 模拟任务执行需要一些时间
select {
case <-time.After(2 * time.Second):
// 这里可以添加实际的任务逻辑,返回结果或错误
if input%2 == 0 {
return input * 2, nil
}
return 0, fmt.Errorf("task failed for input %d", input)
case <-ctx.Done():
// 当 context 被取消时,返回取消错误
return 0, ctx.Err()
}
}
func main() {
// 创建一个可取消的 context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 模拟任务链的输入
input := 3
// 启动第一个协程
var result int
errChan := make(chan error, 1)
go func() {
var err error
result, err = task(ctx, input)
if err != nil {
errChan <- err
cancel() // 一旦某个任务失败,取消整个任务链
}
close(errChan)
}()
// 启动后续协程(这里假设只有一个后续协程作为示例)
var finalResult int
go func() {
select {
case err := <-errChan:
if err != nil {
fmt.Println("Error:", err)
return
}
case <-ctx.Done():
return
}
// 这里使用前一个任务的结果作为输入
var err error
finalResult, err = task(ctx, result)
if err != nil {
fmt.Println("Error in second task:", err)
}
}()
// 等待所有协程完成
time.Sleep(5 * time.Second)
fmt.Println("Final Result:", finalResult)
}
在上述代码中:
task
函数模拟一个具体的任务,它接受context.Context
和输入数据。通过select
语句监听time.After
和ctx.Done
,当任务执行超时或者context
被取消时,相应地返回错误。- 在
main
函数中,首先创建一个可取消的context
,然后启动第一个协程执行任务。如果第一个任务出错,通过errChan
通知并调用cancel
取消整个任务链。后续协程依赖前一个任务的结果,同样会监听context
的取消信号,确保任务链有序取消,并在出错时进行相应处理。