Go 实现父子任务进度同步

场景背景

在很多实际应用中,我们会遇到需要同时追踪多个子任务进度的情况,常见场景包括:

  • 分布式任务处理:一个大的任务被分解为多个子任务,每个子任务执行不同的工作单元。父任务的进度应当基于所有子任务的进度。
  • 多阶段工作流:任务被拆分成多个阶段,父任务的进度依赖于各个阶段的执行情况。
  • 并行处理:多个子任务并行执行,最终汇总父任务的进度。

为了同步这些任务的进度,我们通常需要通过某种方式将子任务的进度反馈给父任务。回调机制便是解决这一问题的有效方式。

回调机制:父子任务进度同步

核心思路

我们通过回调函数将父任务的进度更新传递给子任务。每当子任务执行进度更新时,它会通过回调通知父任务更新其进度。

步骤概述

  1. 父任务进度回调:父任务会在初始化时传入回调函数,每次进度更新时会调用该回调。
  2. 子任务进度回调:子任务在更新自己进度时,调用父任务的回调函数,从而更新父任务的进度。
  3. 进度同步:通过这种回调机制,父任务的进度能够随着子任务的完成而动态变化。

代码实现

我们定义了一个 StageStepper 结构来表示任务的进度管理器。该结构提供了进度步进、进度更新、以及完成任务的功能。

StageStepper 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package main

import "fmt"

// StageProgress 接口定义了任务的基本操作
type StageProgress interface {
Step() // 按默认步长推进
StepBy(n int) // 按自定义步长推进
Finish() // 完成任务
Percent() int // 获取当前进度的百分比
}

// StageStepper 结构体实现了 StageProgress 接口
type StageStepper struct {
total int // 总步数
current int // 当前步数
defaultStep int // 默认步长
onUpdate func(delta, percent int) // 进度更新回调函数
}

// 创建 StageStepper 实例
func NewStageStepper(total int, defaultStep int, onUpdate func(delta, percent int)) StageProgress {
if total <= 0 {
total = 1
}
if defaultStep <= 0 {
defaultStep = 1
}
if onUpdate == nil {
onUpdate = func(delta, percent int) {}
}

return &StageStepper{
total: total,
current: 0,
defaultStep: defaultStep,
onUpdate: onUpdate,
}
}

// 步进
func (s *StageStepper) Step() {
s.StepBy(s.defaultStep)
}

// 按自定义步长推进
func (s *StageStepper) StepBy(n int) {
if n <= 0 || s.current >= s.total {
return
}

remain := s.total - s.current
if n > remain {
n = remain
}

s.current += n
s.emit(n)
}

// 完成任务
func (s *StageStepper) Finish() {
remain := s.total - s.current
if remain > 0 {
s.current = s.total
s.emit(remain)
}
}

// 发出进度更新
func (s *StageStepper) emit(delta int) {
if delta <= 0 {
return
}
s.onUpdate(delta, s.Percent())
}

// 计算进度百分比
func (s *StageStepper) Percent() int {
if s.total <= 0 {
return 100
}
if s.current <= 0 {
return 0
}
if s.current >= s.total {
return 100
}
return (s.current*100 + s.total/2) / s.total
}

使用示例

在父任务和子任务之间同步进度时,我们需要为每个任务设置回调。子任务在执行时,会通过回调更新父任务的进度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func main() {
// 使用 WaitGroup 来同步父任务和子任务
var wg sync.WaitGroup

// 父任务进度更新的回调
parentTask := NewStageStepper(150, 10, func(delta, percent int) {
fmt.Printf("Parent Task Progress: %d%% (Step: %d)\n", percent, delta)
})

// 子任务进度更新的回调
subTask := NewStageStepper(50, 5, func(delta, percent int) {
fmt.Printf("SubTask Progress: %d%% (Step: %d)\n", percent, delta)
// 每次子任务步进时,通知父任务更新进度
parentTask.StepBy(delta)
})

// 执行父任务
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
parentTask.Step()
}
}()

// 执行子任务
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
subTask.Step()
}
}()

// 等待所有 goroutine 完成
wg.Wait()

// 完成任务
parentTask.Finish()
subTask.Finish()
}

进度同步细节

1.父任务总步数设置为 150 步:

父任务的总步数设置为 150,其中包括了父任务的 100 步和子任务的 50 步。父任务的进度条总进度量综合了父任务和所有子任务的步数。

2. 进度计算:

父任务的进度计算同时考虑了父任务自身的进度和所有子任务的进度。在子任务完成一定步数时,父任务的进度会按比例更新。具体地,子任务的进度直接影响父任务的进度,确保父任务进度条准确反映出父任务和子任务的整体执行情况。

3. 回调机制:

通过回调机制,子任务在每次步进时,会通过回调更新父任务的进度。在子任务的进度更新回调函数中,每次子任务步进时,都会通知父任务通过 parentTask.StepBy(delta) 更新父任务的进度。这样,父任务进度条的显示会实时同步到子任务的执行情况。

输出示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
SubTask Progress: 10% (Step: 5)
Parent Task Progress: 10% (Step: 5)
SubTask Progress: 20% (Step: 5)
Parent Task Progress: 13% (Step: 5)
SubTask Progress: 30% (Step: 5)
Parent Task Progress: 17% (Step: 5)
SubTask Progress: 40% (Step: 5)
Parent Task Progress: 20% (Step: 5)
SubTask Progress: 50% (Step: 5)
Parent Task Progress: 23% (Step: 5)
SubTask Progress: 60% (Step: 5)
Parent Task Progress: 27% (Step: 5)
SubTask Progress: 70% (Step: 5)
Parent Task Progress: 30% (Step: 5)
SubTask Progress: 80% (Step: 5)
Parent Task Progress: 33% (Step: 5)
SubTask Progress: 90% (Step: 5)
Parent Task Progress: 37% (Step: 5)
SubTask Progress: 100% (Step: 5)
Parent Task Progress: 40% (Step: 5)
Parent Task Progress: 7% (Step: 10)
Parent Task Progress: 47% (Step: 10)
Parent Task Progress: 53% (Step: 10)
Parent Task Progress: 60% (Step: 10)
Parent Task Progress: 67% (Step: 10)
Parent Task Progress: 73% (Step: 10)
Parent Task Progress: 80% (Step: 10)
Parent Task Progress: 87% (Step: 10)
Parent Task Progress: 93% (Step: 10)
Parent Task Progress: 100% (Step: 10)
...

适用场景

任务总量步进可知的情况

适用于父任务和子任务的进度总量已知的场景,通过综合计算父任务和子任务的总进度,确保父任务进度条平滑更新,避免进度条跳动不流畅。

父子任务进度汇总

在多子任务场景下,父任务进度需要动态调整,确保子任务完成时父任务进度准确反映所有子任务的执行情况。

总结

通过回调机制,我们可以在不改变现有设计结构的前提下,灵活地实现父子任务的进度同步。每当子任务步进时,它会通过回调函数通知父任务更新进度,确保父任务的进度始终反映子任务的完成情况。

这种方法在处理多级任务、并行任务及分布式系统中的任务进度管理时尤为有效。希望这篇博客能够帮助你理解如何利用 Go 中的回调机制来实现任务进度同步,从而优化你的任务调度和进度管理。