GO's sync.WaitGroup and sync.Once

GO's sync.WaitGroup and sync.Once

1. sync.WaitGroupSimple and practical

Before, we used channels to wait for other goroutines to complete in the main goroutine:

func coordinateWithChan() {
    
    
	sign := make(chan struct{
    
    }, 2)
	num := int32(0)
	fmt.Printf("The number: %d [with chan struct{}]\n", num)
	max := int32(10)
	go addNum(&num, 1, max, func() {
    
    
		sign <- struct{
    
    }{
    
    }
	})
	go addNum(&num, 2, max, func() {
    
    
		sign <- struct{
    
    }{
    
    }
	})
	<-sign
	<-sign
}

Actually, you can do it in a simpler way, using sync.WaitGroup:

func coordinateWithWaitGroup() {
    
    
	var wg sync.WaitGroup
	wg.Add(2)
	num := int32(0)
	fmt.Printf("The number: %d [with sync.WaitGroup]\n", num)
	max := int32(10)
	go addNum(&num, 3, max, wg.Done)
	go addNum(&num, 4, max, wg.Done)
	wg.Wait()
}

The WaitGroup type of the sync package. It is more suitable for implementing this one-to-many goroutine coordination process than channels.

sync.WaitGrouptype (hereinafter referred to as the WaitGroup type) is out of the box and is also concurrency safe. At the same time, once it is actually used, it cannot be copied.

The WaitGroup type has three pointer methods: Add, Done, and Wait.

(1) Add method

It is conceivable that there is a counter in this type, and its default value is 0. We can increment or decrement the value of this counter by calling the Add method of this type of value.

(2) Done method

Use this method to keep track of the number of goroutines that need to wait. Correspondingly, the Done method of this type is used to decrement the value of the counter in its own value by one. We can call it through the defer statement in the goroutine that needs to wait.

(3) Wait method

The function of this type of Wait method is to block the current goroutine until the counter in its own value is zeroed. If the value of that counter is 0 when this method is called, then it will do nothing.

2. Can the value of the counter in the sync.WaitGroup type value be less than 0?

Can't.

The reason why the value of the counter in the WaitGroup value cannot be less than 0 is because this will cause a panic. This is true for both the Done method and the Add method that inappropriately call such values.

  • Although the WaitGroup value itself does not need to be initialized, it is still very necessary to increase the value of its counter as soon as possible.
  • The WaitGroup value can be reused, but the integrity of its counting cycle needs to be guaranteed.
  • Do not put the operation of increasing its counter value and the code of calling its Wait method in different goroutines. In other words, it is necessary to prevent the concurrent execution of two operations on the same WaitGroup value.

three,sync.Once

sync.OnceAlso belongs to the structure type, which is also out of the box and concurrency safe. Since this type contains a sync.Mutextype field, copying the value of this type will also cause the function to fail.

type Once struct {
    
    
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

(1) Usage

OnceA method of type Doaccepts only one parameter, and the type of this parameter must be func(), that is, a function with no parameter declaration and result declaration.

The function of this method is not to execute each parameter function only once, but to only execute the function "passed in when it is called for the first time", and will not execute any parameter functions afterwards.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
    
    
	var counter uint32
	var once sync.Once
	once.Do(func() {
    
    
		atomic.AddUint32(&counter, 1)
	})
	fmt.Printf("The counter: %d\n", counter)
	once.Do(func() {
    
    
		atomic.AddUint32(&counter, 2)
	})
	fmt.Printf("The counter: %v\n", counter)
	fmt.Println()
}
$ go run demo02.go
The counter: 1
The counter: 1

$

sync.OnceSo, if you have multiple functions that only need to be executed once, you should assign a value of a type to each of them .

(2) sync.OnceFields of type uint32 in type

sync.OnceThe type has a donefield named uint32. Its role is to record the number of times the Do method of its value is called. The value of this field can only be 0 or 1.

Once the Do method is first called, its value changes from 0 to 1.

The uint32 type is used to guarantee atomicity.

The modification doneuses the "double judgment + lock" method, which is similar to the singleton pattern in the GoF design pattern.

func (o *Once) Do(f func()) {
    
    
	if atomic.LoadUint32(&o.done) == 0 {
    
    
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
    
    
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
    
    
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

(3) Functional characteristics of the Do method

The first feature: the Do method will only change the value of the done field to 1 after the execution of the parameter function is completed. Therefore, if the execution of the parameter function takes a long time or will not end at all (such as performing some guardian tasks), Then it may cause simultaneous blocking of related goroutines.

The second feature: after the execution of the parameter function, the Do method uses an atomic operation to assign the done field, and this operation is hung in the defer statement. Therefore, no matter how the execution of the parameter function will end, the value of the done field will become 1.

That is to say, even if this parameter function is not successfully executed (for example, a panic is triggered), we cannot re-execute it with the same Once value. Therefore, if you need to set a retry mechanism for the execution of the parameter function, then you must consider the timely replacement of the Once value.

Guess you like

Origin blog.csdn.net/hefrankeleyn/article/details/128606310