github.com/haraldrudell/parl@v0.4.176/once-ch.go (about) 1 /* 2 © 2023-present Harald Rudell <haraldrudell@proton.me> (https://haraldrudell.github.io/haraldrudell/) 3 All rights reserved 4 */ 5 6 package parl 7 8 import "sync/atomic" 9 10 const ( 11 // [OnceCh.IsWinner] loser threads do not wait 12 NoOnceWait = true 13 // [OnceCh.IsWinner] loser threads wait 14 LoserWait = false 15 ) 16 17 // OnceCh implements a one-time execution filter 18 // - initialization free 19 // - OnceCh is similar to [sync.Once] with improvements: 20 // - — does not require an awkward function value to be provided. 21 // Method dunction-values cause allocation 22 // - — awaitable channel mechanic means threads can 23 // await multiple events 24 // - — is observable via [OnceCh.IsInvoked] [OnceCh.IsClosed] 25 // 26 // Usage: 27 // 28 // var o OnceCh 29 // if isWinner, done := o.IsWinner(); !isWinner { 30 // return // thread already waited for winner thread completion 31 // } else { 32 // defer done.Done() 33 // } 34 // … 35 type OnceCh struct { 36 // winner selects the winning thread 37 winner atomic.Bool 38 // done allows: 39 // - winner thread to indicate completion 40 // - loser threads to await winner completion 41 done doneOnce 42 } 43 44 type doneOnce struct { 45 // awaitable is mechanic for loser threads to 46 // await winner execution complete 47 awaitable Awaitable 48 } 49 50 var _ Done = &doneOnce{} 51 52 func (d *doneOnce) Done() { 53 d.awaitable.Close() 54 } 55 56 // IsWinner selects winner thread as the first of invokers 57 // - noWait missing or LoserWait: loser thread wait for winner thread invoking done.Done 58 // - noWait NoOnceWait: eventually consistent: loser threads immediately return 59 // - isWinner true: this is the winner first invocation. 60 // - — must invoke done.Done upon task completion 61 // - isWinner false: loser thread, done is nil. 62 // May have already awaited winner thread completion 63 func (o *OnceCh) IsWinner(noWait ...bool) (isWinner bool, done Done) { 64 65 // pick winner thread 66 if isWinner = o.winner.CompareAndSwap(false, true); isWinner { 67 done = &o.done 68 return // winner return 69 } 70 71 // loser threads wait for winner thread unless noWait: NoOnceWait 72 if len(noWait) == 0 || !noWait[0] { 73 <-o.done.awaitable.Ch() 74 } 75 76 return 77 } 78 79 // Ch returns a channel that closes once IsWinner and done have both been invoked 80 func (o *OnceCh) Ch() (ch AwaitableCh) { return o.done.awaitable.Ch() } 81 82 // IsInvoked indicates that a winner was selected 83 func (o *OnceCh) IsInvoked() (isInvoked bool) { return o.winner.Load() } 84 85 // IsClosed indicates that a winner was selected and invoked done 86 func (o *OnceCh) IsClosed() (isClosed bool) { return o.done.awaitable.IsClosed() }