amtoaer

晓风残月

叹息似的渺茫,你仍要保存着那真!
github
x
telegram
steam
nintendo switch
email

Application scenarios of sync.cond in Go (including examples and comparisons)

title: Application Scenarios of sync.Cond in Go (with Examples and Comparisons)
date: 2021-12-04 12:22:19
tags: ['go']#

Recently, while studying "High Performance Go Programming" (https://geektutu.com/post/high-performance-go.html), I came across the concept of sync.Cond condition variables in Go, and I found it a bit difficult to understand. After researching, I gained some understanding of it and decided to document it.

Concept#

The sync package provides the condition variable type sync.Cond, which can be used in combination with a mutex or a read-write lock to coordinate goroutines that want to access shared variables. Its main mechanism is to notify other goroutines that are blocked due to changes in the corresponding shared resource state.

Definition#

sync.Cond is a struct, defined as follows:

// Each Cond has an associated Locker L (often 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.
type Cond struct {
        noCopy noCopy

        // L is held while observing or changing the condition
        L Locker

        notify  notifyList
        checker copyChecker
}

This type can be created using the func NewCond(l Locker) *Cond method in the sync package.

It has the following three methods:

  • func (c *Cond) Broadcast()

    Wakes up all goroutines waiting for c.

  • func (c *Cond) Signal()

    Wakes up a single goroutine waiting for c (if any).

  • func (c *Cond) Wait()

    Automatically unlocks c.L and suspends the execution of the goroutine until it is awakened. This function re-locks c.L before returning.

Application Scenarios#

Based on the above description, the main usage of this structure should be when multiple goroutines call Wait to wait for a goroutine to run, and the waiting goroutines are awakened by calling Broadcast or Signal. In other words, it is mainly used in scenarios where multiple goroutines are waiting and one goroutine notifies the others.

Here's a small example from daily life:

Several students arrive at the cafeteria, but the cafeteria hasn't finished cooking yet, so the students need to wait until the cafeteria finishes cooking before they can eat.

Using the sync.Cond to describe the above scenario, the following code can be written:

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	ok      = false                           // Whether the cooking is done
	food    = ""                              // Name of the food
	rwMutex = &sync.RWMutex{}                 // Read-write lock used to lock the modification of the food name
	cond    = sync.NewCond(rwMutex.RLocker()) // Condition variable using the read lock of the read-write lock
)

func makeFood() {
	// Use a write lock for cooking (of course, because there is only one cooking goroutine, this lock has no practical meaning)
	rwMutex.Lock()
	defer rwMutex.Unlock()
	fmt.Println("The cafeteria starts cooking!")
	time.Sleep(3 * time.Second)
	ok = true
	food = "Fish-flavored Shredded Pork"
	fmt.Println("The cafeteria has finished cooking!")
	cond.Broadcast()
}

func waitToEat() {
	cond.L.Lock()
	defer cond.L.Unlock()
	for !ok {
		cond.Wait()
	}
	fmt.Println("Finally, I can eat. This meal is", food)
}

func main() {
	for i := 0; i < 3; i++ {
		go waitToEat()
	}
	go makeFood()
	time.Sleep(4 * time.Second)
}

Running this program produces the following result:

The cafeteria starts cooking!
The cafeteria has finished cooking!
Finally, I can eat. This meal is Fish-flavored Shredded Pork
Finally, I can eat. This meal is Fish-flavored Shredded Pork
Finally, I can eat. This meal is Fish-flavored Shredded Pork

Differences from Other Concepts#

channel#

As we all know, channel is also a concept used for goroutine synchronization in Go, but unlike sync.Cond, channel is mostly used for one-to-one communication. If we extend the previous example, the scenario this time would be:

A student arrives at the cafeteria, but the cafeteria hasn't finished cooking yet, so the student needs to wait until the cafeteria finishes cooking before eating.

Using channel to describe this:

package main

import (
	"fmt"
	"time"
)

var (
	food = ""
	ch   = make(chan struct{})
)

func makeFood() {
	fmt.Println("The cafeteria starts cooking!")
	time.Sleep(3 * time.Second)
	food = "Fish-flavored Shredded Pork"
	fmt.Println("The cafeteria has finished cooking!")
	ch <- struct{}{}
}

func waitToEat() {
	<-ch
	fmt.Println("Finally, I can eat. This meal is", food)
}

func main() {
	go waitToEat()
	go makeFood()
	time.Sleep(4 * time.Second)
}

The result of running this program is:

The cafeteria starts cooking!
The cafeteria has finished cooking!
Finally, I can eat. This meal is Fish-flavored Shredded Pork

sync.WaitGroup#

sync.WaitGroup is also used for goroutine synchronization, but its application scenario is the opposite of sync.Cond. The latter is mostly used for multiple goroutines waiting and one goroutine notifying, while the former is mostly used for one goroutine waiting for multiple goroutines to finish.

Using the same example:

A student arrives at the cafeteria, but none of the cafeteria windows have finished cooking yet. The student wants to wait until each window finishes cooking and then order a portion of food from each window. (This is a bit far-fetched XD)

The code can be described as follows:

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	wg sync.WaitGroup
)

func makeFood() {
	fmt.Println("The window starts cooking!")
	time.Sleep(3 * time.Second)
	fmt.Println("The window has finished cooking!")
	wg.Done()
}

func waitToEat() {
	wg.Wait()
	fmt.Println("Oh, one portion from each window!")
}

func main() {
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go makeFood()
	}
	go waitToEat()
	time.Sleep(4 * time.Second)
}

The result of running this program is:

The window starts cooking!
The window starts cooking!
The window starts cooking!
The window has finished cooking!
The window has finished cooking!
The window has finished cooking!
Oh, one portion from each window!

Conclusion#

The above is my shallow understanding. If there are any mistakes, please feel free to point them out in the comments. Thank you!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.