面试题答案
一键面试实现思路
- 任务依赖建模:使用图(Graph)数据结构来表示任务之间的依赖关系。可以用邻接表等方式存储,每个节点代表一个任务,边表示任务间的依赖。
- 任务执行调度:利用Go语言的通道(Channel)来协调任务的执行顺序。只有当一个任务所依赖的所有任务都完成时,该任务才会被放入一个待执行队列,进而被Go协程执行。
- Context控制:通过
context.Context
来实现任务流程的取消和超时控制。context.Context
可以传递到每个Go协程中,在协程内部通过ctx.Done()
通道来监听取消信号。
关键代码示例
package main
import (
"context"
"fmt"
"sync"
)
// 定义任务结构体
type Task struct {
ID int
DependsOn []int
Execute func(ctx context.Context) error
}
// 执行任务的函数
func executeTask(ctx context.Context, task Task, wg *sync.WaitGroup, resultChan chan<- error) {
defer wg.Done()
err := task.Execute(ctx)
resultChan <- err
}
// 调度任务执行
func scheduleTasks(ctx context.Context, tasks []Task) error {
var wg sync.WaitGroup
resultChan := make(chan error, len(tasks))
taskDone := make(map[int]bool)
// 依赖关系检查和任务执行
for {
readyToExecute := false
for _, task := range tasks {
if taskDone[task.ID] {
continue
}
allDependsDone := true
for _, depID := range task.DependsOn {
if!taskDone[depID] {
allDependsDone = false
break
}
}
if allDependsDone {
readyToExecute = true
wg.Add(1)
go executeTask(ctx, task, &wg, resultChan)
taskDone[task.ID] = true
}
}
if!readyToExecute {
break
}
}
go func() {
wg.Wait()
close(resultChan)
}()
// 检查任务执行结果
for err := range resultChan {
if err != nil {
return err
}
}
return nil
}
Context的传递和生效
- 传递:
context.Context
通过函数参数传递到每个执行任务的Go协程中。例如在executeTask
函数中,ctx
作为参数传入。 - 生效:在每个任务的执行函数
task.Execute
内部,可以通过ctx.Done()
通道监听取消信号。如果ctx.Done()
通道接收到数据,说明Context被取消或超时,任务应尽快结束执行。例如:
func myTaskExecute(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 任务正常执行逻辑
fmt.Println("Task is running")
return nil
}
}
在上述代码中,当ctx.Done()
通道接收到信号时,任务通过返回ctx.Err()
来结束执行,从而实现整个任务流程的取消或超时控制。