cond.mx raw

   1  package sync
   2  
   3  import (
   4  	"internal/task"
   5  	"unsafe"
   6  )
   7  
   8  // Condition variable.
   9  // A goroutine that called Wait() can be in one of a few states depending on the
  10  // Task.Data field:
  11  // - When entering Wait, and before going to sleep, the data field is 0.
  12  // - When the goroutine that calls Wait changes its data value from 0 to 1, it
  13  //   is going to sleep. It has not been awoken early.
  14  // - When instead a call to Signal or Broadcast can change the data field from 0
  15  //   to 1, it will _not_ go to sleep but be signalled early.
  16  //   This can happen when a concurrent call to Signal happens, or the Unlock
  17  //   function calls Signal for some reason.
  18  
  19  type Cond struct {
  20  	L Locker
  21  
  22  	blocked task.Stack
  23  	lock    task.PMutex
  24  }
  25  
  26  func NewCond(l Locker) *Cond {
  27  	return &Cond{L: l}
  28  }
  29  
  30  func (c *Cond) trySignal() bool {
  31  	// Pop a blocked task off of the stack, and schedule it if applicable.
  32  	t := c.blocked.Pop()
  33  	if t != nil {
  34  		dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data))
  35  
  36  		// The data value is 0 when the task is not yet sleeping, and 1 when it is.
  37  		if dataPtr.Swap(1) != 0 {
  38  			// The value was already 1, so the task went to sleep (or is about to go
  39  			// to sleep). Schedule the task to be resumed.
  40  			scheduleTask(t)
  41  		}
  42  		return true
  43  	}
  44  
  45  	// There was nothing to signal.
  46  	return false
  47  }
  48  
  49  func (c *Cond) Signal() {
  50  	c.lock.Lock()
  51  	c.trySignal()
  52  	c.lock.Unlock()
  53  }
  54  
  55  func (c *Cond) Broadcast() {
  56  	// Signal everything.
  57  	c.lock.Lock()
  58  	for c.trySignal() {
  59  	}
  60  	c.lock.Unlock()
  61  }
  62  
  63  func (c *Cond) Wait() {
  64  	// Mark us as not yet signalled or sleeping.
  65  	t := task.Current()
  66  	dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data))
  67  	dataPtr.Store(0)
  68  
  69  	// Add us to the list of waiting goroutines.
  70  	c.lock.Lock()
  71  	c.blocked.Push(t)
  72  	c.lock.Unlock()
  73  
  74  	// Temporarily unlock L.
  75  	c.L.Unlock()
  76  
  77  	// Re-acquire the lock before returning.
  78  	defer c.L.Lock()
  79  
  80  	// If we were signaled while unlocking, immediately complete.
  81  	if dataPtr.Swap(1) != 0 {
  82  		// The data value was already 1, so we got a signal already (and weren't
  83  		// scheduled because trySignal was the first to change the value).
  84  		return
  85  	}
  86  
  87  	// We were the first to change the value from 0 to 1, meaning we did not get
  88  	// a signal during the call to Unlock(). So we wait until we do get a
  89  	// signal.
  90  	task.Pause()
  91  }
  92  
  93  //go:linkname scheduleTask runtime.scheduleTask
  94  func scheduleTask(*task.Task)
  95