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  }