面试题答案
一键面试设计思路
- 任务分块:由于任务执行时间差异大且数量动态变化,将任务按照一定规则分成多个小块,这样可以更细粒度地控制并发。例如,可以根据任务的预计执行时间或其他属性进行分块。
- 动态调整并发数:使用一个控制变量来动态调整同时执行的任务数量。根据系统资源状况(如CPU使用率、内存占用等)或者已完成任务的反馈来调整这个并发数。
- WaitGroup使用:为每个任务块创建一个WaitGroup,当任务块中的所有任务完成时,通过WaitGroup来通知主线程。这样主线程无需等待所有任务全部完成,而是可以分块等待,提高并发控制精度。
关键代码实现(以Go语言为例)
package main
import (
"fmt"
"sync"
"time"
)
func task(id int, duration time.Duration, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Task %d started\n", id)
time.Sleep(duration)
fmt.Printf("Task %d finished\n", id)
}
func main() {
// 模拟动态变化的任务列表
tasks := []struct {
id int
duration time.Duration
}{
{1, 2 * time.Second},
{2, 5 * time.Second},
{3, 1 * time.Second},
{4, 3 * time.Second},
}
maxConcurrent := 2 // 初始最大并发数
var wg sync.WaitGroup
taskCh := make(chan struct{}, maxConcurrent)
for _, t := range tasks {
taskCh <- struct{}{}
wg.Add(1)
go func(id int, duration time.Duration) {
defer func() { <-taskCh }()
task(id, duration, &wg)
}(t.id, t.duration)
}
wg.Wait()
close(taskCh)
fmt.Println("All tasks completed")
}
task
函数:每个任务的具体执行逻辑,执行完成后调用wg.Done()
通知WaitGroup。main
函数:- 定义任务列表。
- 设置初始最大并发数
maxConcurrent
,并使用一个带缓冲的通道taskCh
来控制并发数量。 - 遍历任务列表,向通道
taskCh
发送信号以获取执行权,增加WaitGroup计数,然后启动协程执行任务。任务执行完后从通道中取出信号,释放执行权。 - 最后调用
wg.Wait()
等待所有任务完成,并关闭通道。
通过这种方式,可以在保证任务高效执行的同时,避免资源过度占用,实现基于WaitGroup的并发控制精度优化。