Go Cond: Difference between revisions
(Created page with "=Internal= * The <tt>sync</tt> Package =Overview=") |
|||
(25 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=External= | |||
* https://pkg.go.dev/sync#Cond | |||
=Internal= | =Internal= | ||
* [[Go_Package_sync#Cond|The <tt>sync</tt> Package]] | * [[Go_Package_sync#Cond|The <tt>sync</tt> Package]] | ||
=Overview= | =Overview= | ||
<code>Cond</code> implements a '''condition variable''', a rendezvous point for goroutines waiting for or announcing the occurrence of an event. In this definition, an "event" is an arbitrary signal between two or more goroutines that carries no information other than the fact that it has occurred. | |||
<code>Cond#Wait()</code> offers a way for a goroutine to "efficiently sleep" (be suspended), until it is signaled to wake up and check of a condition, by other calling <code>Signal()</code> or <code>Broadcast()</code>. Each goroutine that wants to access condition, concurrently, uses this access pattern: | |||
<syntaxhighlight lang='go'> | |||
cond.L.Lock() | |||
for !condition() { | |||
c.Wait() // Wait() releases the lock internally, as a side effect | |||
} | |||
// At this point the goroutine has exclusive access to the condition. | |||
// Make use of the condition, accessing it is concurrently-safe. | |||
... | |||
c.L.Unlock() | |||
// Ensure that other goroutine competing for the condition are woken up with Broadcast() or Signal() | |||
</syntaxhighlight> | |||
To wake just one goroutine that called <code>Wait()</code> and are now suspended, use <code>Signal()</code>. Internally, the runtime maintains a FIFO list of goroutines waiting to be signaled, <code>Signal()</code> finds the goroutine that’s been waiting the longest and notifies it. | |||
To wake up all goroutines that called <code>Wait()</code> and are now suspended, use <code>Broadcast()</code>. | |||
<span id='Close_as_Broadcast'></span>For many simple use cases, users will be better off '''using channels''' than a <code>Cond</code>, <code>Broadcast()</code> corresponds to [[Go_Channels#Close_as_Broadcast|closing a channel]], and <code>Signal</code> corresponds to sending on a channel. However, <code>Cond</code> is much more performant than using channels. | |||
Each <code>Cond</code> has an associated <code>Locker</code> <code>L</code>, commonly a <code>*Mutex</code> or <code>*RWMutex</code>, which must be held when changing the condition and when calling the <code>Wait</code> method. A <code>Cond</code> must not be copied after first use. In the terminology of the Go memory model, <code>Cond</code> arranges that a call to <code>Broadcast()</code> or <code>Signal()</code> "synchronizes before" any <code>Wait</code> call that it unblocks. | |||
=Example= | |||
<syntaxhighlight lang='go'> | |||
var cond = sync.NewCond(&sync.Mutex{}) | |||
var remaining = []string{"red", "green", "blue", "yellow", "black", "fuchsia"} | |||
var next string | |||
func painter(myColor string) { | |||
cond.L.Lock() | |||
// wait for my turn, "sleep" otherwise | |||
for next != myColor { | |||
cond.Wait() | |||
} | |||
// it is my turn, paint | |||
fmt.Printf("%s\n", myColor) | |||
// remove myself from the remaining | |||
var s []string | |||
for _, c := range remaining { | |||
if c == myColor { | |||
continue | |||
} | |||
s = append(s, c) | |||
} | |||
remaining = s | |||
// designate a successor | |||
if len(remaining) > 0 { | |||
next = remaining[rand.Intn(len(remaining))] | |||
} | |||
// release the lock | |||
cond.L.Unlock() | |||
// notify everyone else | |||
cond.Broadcast() | |||
} | |||
func main() { | |||
var painters = make([]string, len(remaining)) | |||
copy(painters, remaining) | |||
for _, c := range painters { | |||
go painter(c) | |||
} | |||
// pick a random first painter | |||
next = remaining[rand.Intn(len(remaining))] | |||
cond.Broadcast() | |||
// wait until all painters painted | |||
cond.L.Lock() | |||
for len(remaining) > 0 { | |||
cond.Wait() | |||
} | |||
fmt.Printf("all painters done") | |||
cond.L.Unlock() | |||
} | |||
</syntaxhighlight> |
Latest revision as of 02:12, 22 January 2024
External
Internal
Overview
Cond
implements a condition variable, a rendezvous point for goroutines waiting for or announcing the occurrence of an event. In this definition, an "event" is an arbitrary signal between two or more goroutines that carries no information other than the fact that it has occurred.
Cond#Wait()
offers a way for a goroutine to "efficiently sleep" (be suspended), until it is signaled to wake up and check of a condition, by other calling Signal()
or Broadcast()
. Each goroutine that wants to access condition, concurrently, uses this access pattern:
cond.L.Lock()
for !condition() {
c.Wait() // Wait() releases the lock internally, as a side effect
}
// At this point the goroutine has exclusive access to the condition.
// Make use of the condition, accessing it is concurrently-safe.
...
c.L.Unlock()
// Ensure that other goroutine competing for the condition are woken up with Broadcast() or Signal()
To wake just one goroutine that called Wait()
and are now suspended, use Signal()
. Internally, the runtime maintains a FIFO list of goroutines waiting to be signaled, Signal()
finds the goroutine that’s been waiting the longest and notifies it.
To wake up all goroutines that called Wait()
and are now suspended, use Broadcast()
.
For many simple use cases, users will be better off using channels than a Cond
, Broadcast()
corresponds to closing a channel, and Signal
corresponds to sending on a channel. However, Cond
is much more performant than using channels.
Each Cond
has an associated Locker
L
, commonly a *Mutex
or *RWMutex
, which must be held when changing the condition and when calling the Wait
method. A Cond
must not be copied after first use. In the terminology of the Go memory model, Cond
arranges that a call to Broadcast()
or Signal()
"synchronizes before" any Wait
call that it unblocks.
Example
var cond = sync.NewCond(&sync.Mutex{})
var remaining = []string{"red", "green", "blue", "yellow", "black", "fuchsia"}
var next string
func painter(myColor string) {
cond.L.Lock()
// wait for my turn, "sleep" otherwise
for next != myColor {
cond.Wait()
}
// it is my turn, paint
fmt.Printf("%s\n", myColor)
// remove myself from the remaining
var s []string
for _, c := range remaining {
if c == myColor {
continue
}
s = append(s, c)
}
remaining = s
// designate a successor
if len(remaining) > 0 {
next = remaining[rand.Intn(len(remaining))]
}
// release the lock
cond.L.Unlock()
// notify everyone else
cond.Broadcast()
}
func main() {
var painters = make([]string, len(remaining))
copy(painters, remaining)
for _, c := range painters {
go painter(c)
}
// pick a random first painter
next = remaining[rand.Intn(len(remaining))]
cond.Broadcast()
// wait until all painters painted
cond.L.Lock()
for len(remaining) > 0 {
cond.Wait()
}
fmt.Printf("all painters done")
cond.L.Unlock()
}