mirror of
https://github.com/luxfi/concurrent.git
synced 2026-10-30 16:59:28 +00:00
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package lock
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
)
|
|
|
|
// Cond implements a condition variable. Typically [sync.Cond] should be
|
|
// preferred. However, this implementation supports cancellable waits.
|
|
type Cond struct {
|
|
L sync.Locker
|
|
|
|
m sync.Mutex // protects the waiters map
|
|
w map[chan struct{}]struct{}
|
|
}
|
|
|
|
// NewCond returns a new Cond with Locker l.
|
|
func NewCond(l sync.Locker) *Cond {
|
|
return &Cond{
|
|
L: l,
|
|
w: make(map[chan struct{}]struct{}),
|
|
}
|
|
}
|
|
|
|
// Wait atomically unlocks c.L and suspends execution of the calling goroutine.
|
|
// After later resuming execution, Wait locks c.L before returning. Unlike in
|
|
// other systems, Wait cannot return unless awoken by [Cond.Broadcast],
|
|
// [Cond.Signal], or due to the context being cancelled.
|
|
//
|
|
// Because c.L is not locked while Wait is waiting, the caller typically cannot
|
|
// assume that the condition is true when Wait returns, even if the returned
|
|
// value is nil. Instead, the caller should Wait in a loop:
|
|
//
|
|
// c.L.Lock()
|
|
// defer c.L.Unlock()
|
|
//
|
|
// for !condition() {
|
|
// if err := c.Wait(ctx); err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
// ... make use of condition ...
|
|
func (c *Cond) Wait(ctx context.Context) error {
|
|
// Add this thread as a new waiter
|
|
c.m.Lock()
|
|
newL := make(chan struct{})
|
|
c.w[newL] = struct{}{}
|
|
c.m.Unlock()
|
|
|
|
c.L.Unlock()
|
|
// We must hold the lock when we return to ensure that the caller can
|
|
// release the lock after wait returns. This is true regardless of if the
|
|
// wait was cancelled or not.
|
|
defer c.L.Lock()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
// Since the wait was cancelled, we remove our waiting channel on a
|
|
// best-effort basis.
|
|
c.m.Lock()
|
|
delete(c.w, newL)
|
|
c.m.Unlock()
|
|
|
|
return ctx.Err()
|
|
case <-newL:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Signal wakes one goroutine waiting on c, if there is any.
|
|
//
|
|
// It is allowed but not required for the caller to hold c.L during the call.
|
|
//
|
|
// Signal() does not affect goroutine scheduling priority; if other goroutines
|
|
// are attempting to lock c.L, they may be awoken before a "waiting" goroutine.
|
|
func (c *Cond) Signal() {
|
|
c.m.Lock()
|
|
defer c.m.Unlock()
|
|
|
|
for w := range c.w {
|
|
close(w)
|
|
delete(c.w, w)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Broadcast wakes all goroutines waiting on c.
|
|
//
|
|
// It is allowed but not required for the caller to hold c.L during the call.
|
|
func (c *Cond) Broadcast() {
|
|
c.m.Lock()
|
|
defer c.m.Unlock()
|
|
|
|
for w := range c.w {
|
|
close(w)
|
|
delete(c.w, w)
|
|
}
|
|
}
|