github.com/rolandhe/saber@v0.0.4/gocc/future.go (about) 1 // Golang concurrent tools like java juc. 2 // 3 // Copyright 2023 The saber Authors. All rights reserved. 4 5 package gocc 6 7 import ( 8 "errors" 9 "sync/atomic" 10 "time" 11 ) 12 13 var ( 14 TimeoutError = errors.New("timeout") 15 TaskCancelledError = errors.New("future task is cancelled") 16 ) 17 18 // NewFutureGroup 构建一个Future组,每组内的Future数量需要事先知道。 19 // 20 // count 组内Future的数量 21 func NewFutureGroup(count uint64) *FutureGroup { 22 return &FutureGroup{ 23 notifier: NewCountdownLatch(int64(count)), 24 total: int64(count), 25 } 26 } 27 28 func newFuture() *Future { 29 return &Future{ 30 ch: make(chan struct{}), 31 } 32 } 33 34 func newFutureWithGroup(g *FutureGroup) *Future { 35 future := &Future{ 36 ch: make(chan struct{}), 37 groupNotifier: g.notifier, 38 } 39 g.add(future) 40 return future 41 } 42 43 // Future 与java类似,提供任务执行结果占位符的能力,持有Future的线程可以调用: 44 // 45 // # Get 等待结果返回,如果已经调用Cancel方法,则err是TaskCancelledError 46 // 47 // # TryGet, 尝试获取返回结果,无论有无结果都立即返回,有则err是nil,无则返回TimeoutError 48 // 49 // # GetTimeout, 待超时的等待,如果超时时间内有结果则err是nil, 无怎返回TimeoutError 50 // 51 // Cancel, 取消当前任务,执行取消后,任务可以已经被执行,也可能不执行,当前线程可以直接去处理其他事情,无需关心任务的执行结果 52 type Future struct { 53 ch chan struct{} 54 result *taskResult 55 groupNotifier *CountdownLatch 56 canceledFlag atomic.Bool 57 } 58 59 // Get 等待结果返回,如果已经调用Cancel方法,则err是TaskCancelledError 60 func (f *Future) Get() (any, error) { 61 if f.IsCancelled() { 62 return nil, TaskCancelledError 63 } 64 <-f.ch 65 return f.result.r, f.result.e 66 } 67 68 // TryGet 尝试获取返回结果,无论有无结果都立即返回,有则err是nil,无则返回TimeoutError 69 func (f *Future) TryGet() (any, error) { 70 if f.IsCancelled() { 71 return nil, TaskCancelledError 72 } 73 select { 74 case <-f.ch: 75 return f.result.r, f.result.e 76 default: 77 return nil, TimeoutError 78 } 79 } 80 81 // Cancel 取消当前任务,执行取消后,任务可以已经被执行,也可能不执行,当前线程可以直接去处理其他事情,无需关心任务的执行结果 82 func (f *Future) Cancel() { 83 f.canceledFlag.Store(true) 84 } 85 86 // IsCancelled 任务是否被设置为取消状态 87 func (f *Future) IsCancelled() bool { 88 return f.canceledFlag.Load() 89 } 90 91 // GetTimeout 待超时的等待,如果超时时间内有结果则err是nil, 无怎返回TimeoutError 92 func (f *Future) GetTimeout(d time.Duration) (any, error) { 93 if f.IsCancelled() { 94 return nil, TaskCancelledError 95 } 96 if d < 0 { 97 return f.Get() 98 } 99 if d == 0 { 100 return f.TryGet() 101 } 102 103 select { 104 case <-f.ch: 105 return f.result.r, f.result.e 106 case <-time.After(d): 107 return nil, TimeoutError 108 } 109 } 110 111 func (f *Future) accept(v *taskResult) { 112 f.result = v 113 close(f.ch) 114 if f.groupNotifier != nil { 115 f.groupNotifier.Down() 116 } 117 } 118 119 // FutureGroup 用于批量管理任务的组件, 需要同时执行一批任务再等待他们的执行结果的场景可以是FutureGroup, 你可以先设置需要执行任务的个数,然后逐个的加入待执行任务,最后在FutureGroup上等待执行结果. 120 // 121 // 注意: 122 // 123 // 1. FutureGroup需要预先知道待执行任务的个数, 这么设计有两个考虑,一是这符合大多数的使用场景,二是相比动态计算任务数底层无需太多的并发安全考虑,性能更好 124 // 125 // 2. add/Cancel,方法并不是线程安全的,也就是说必须要在一个线程内把所有的任务放入Group, 且在同一个线程内Cancel;get/GetTimeout/TryGet线程安全的 126 // 127 // 3. 切记, Group内的任务数必须和预先设定的个数相同,否则永远也无法等到结束 128 type FutureGroup struct { 129 futureGroup []*Future 130 notifier *CountdownLatch 131 total int64 132 } 133 134 // Get 等待所有任务完成,返回结果 135 func (fg *FutureGroup) Get() []*Future { 136 fg.notifier.Wait() 137 return fg.futureGroup 138 } 139 140 // TryGet 测试所有任务完成,如果完成直接返回结果,否则返回TimeoutError 141 func (fg *FutureGroup) TryGet() ([]*Future, error) { 142 if fg.notifier.TryWait() { 143 return fg.futureGroup, nil 144 } 145 return nil, TimeoutError 146 } 147 148 // GetTimeout 超时时间内测试所有任务完成,如果完成直接返回结果,否则返回TimeoutError 149 func (fg *FutureGroup) GetTimeout(timeout time.Duration) ([]*Future, error) { 150 if fg.notifier.WaitTimeout(timeout) { 151 return fg.futureGroup, nil 152 } 153 return nil, TimeoutError 154 } 155 156 // Cancel 取消所有的任务, 无法保证线程安全, 或者在创建Group的线程内执行,这是大多数场景,或者需要调用者保持线程安全,比如创建一个新的goroutine来执行Cancels 157 func (fg *FutureGroup) Cancel() { 158 for _, future := range fg.futureGroup { 159 future.Cancel() 160 } 161 } 162 163 func (fg *FutureGroup) add(f *Future) { 164 fg.futureGroup = append(fg.futureGroup, f) 165 if len(fg.futureGroup) > int(fg.total) { 166 panic("exceed group size") 167 } 168 }