sync.Once, sync.Map based on go

In the previous words: This is an advanced knowledge point.

In many programming scenarios, we need to ensure that certain operations are executed only once in high-concurrency scenarios, such as loading configuration files only once, and closing channels only once.

The sync package in the Go language provides a solution for scenarios that are executed only once-sync.Once.

sync.Once has only one Do method, and its signature is as follows:

func (o *Once) Do(f func()) {
    
    }

Note: If the function f to be executed needs to pass parameters, it needs to be used with a closure.
Example of loading configuration files
It is a good practice to delay an expensive initialization operation until it is actually used. Because pre-initializing a variable (such as completing the initialization in the init function) will increase the startup time of the program, and it may be useless during the actual execution, then this initialization operation is not necessary. Let's

var icons map[string]image.Image

func loadIcons() {
    
    
	icons = map[string]image.Image{
    
    
		"left":  loadIcon("left.png"),
		"up":    loadIcon("up.png"),
		"right": loadIcon("right.png"),
		"down":  loadIcon("down.png"),
	}
}

// Icon 被多个goroutine调用时不是并发安全的
func Icon(name string) image.Image {
    
    
	if icons == nil {
    
    
		loadIcons()
	}
	return icons[name]
}

The sample code modified using sync.Once is as follows:

var icons map[string]image.Image

var loadIconsOnce sync.Once

func loadIcons() {
    
    
	icons = map[string]image.Image{
    
    
		"left":  loadIcon("left.png"),
		"up":    loadIcon("up.png"),
		"right": loadIcon("right.png"),
		"down":  loadIcon("down.png"),
	}
}

// Icon 是并发安全的
func Icon(name string) image.Image {
    
    
	loadIconsOnce.Do(loadIcons)
	return icons[name]
}

Concurrency safe singleton mode
The following is a concurrency safe singleton mode implemented with sync.Once:

package singleton

import (
    "sync"
)

type singleton struct {
    
    }

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    
    
    once.Do(func() {
    
    
        instance = &singleton{
    
    }
    })
    return instance
}

sync.Once actually contains a mutex lock and a boolean value internally. The mutex lock guarantees the safety of the boolean value and data, and the boolean value is used to record whether the initialization is complete. This design can ensure that the initialization operation is concurrent and safe and the initialization operation will not be executed multiple times.

sync.Map The
built-in map in Go language is not concurrently safe. Consider the following example:

var m = make(map[string]int)

func get(key string) int {
    
    
	return m[key]
}

func set(key string, value int) {
    
    
	m[key] = value
}

func main() {
    
    
	wg := sync.WaitGroup{
    
    }
	for i := 0; i < 20; i++ {
    
    
		wg.Add(1)
		go func(n int) {
    
    
			key := strconv.Itoa(n)
			set(key, n)
			fmt.Printf("k=:%v,v:=%v\n", key, get(key))
			wg.Done()
		}(i)
	}
	wg.Wait()
}

The above code may have no problem when a few goroutines are enabled. When the above code is executed after more concurrency, a fatal error: concurrent map writes error will be reported.

In such a scenario, it is necessary to lock the map to ensure the safety of concurrency. The sync package of the Go language provides an out-of-the-box concurrent safe version of map–sync.Map. Out of the box means that it can be used directly without using the make function initialization like the built-in map. At the same time, sync.Map has built-in operation methods such as Store, Load, LoadOrStore, Delete, Range, etc.

var m = sync.Map{
    
    }

func main() {
    
    
	wg := sync.WaitGroup{
    
    }
	for i := 0; i < 20; i++ {
    
    
		wg.Add(1)
		go func(n int) {
    
    
			key := strconv.Itoa(n)
			m.Store(key, n)
			value, _ := m.Load(key)
			fmt.Printf("k=:%v,v:=%v\n", key, value)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

Atomic operation
The lock operation in the code is time-consuming and costly because it involves context switching in the kernel state. For basic data types, we can also use atomic operations to ensure concurrency safety, because atomic operations are methods provided by the Go language, which can be completed in user mode, so the performance is better than locking operations. Atomic operations in Go language are provided by the built-in standard library sync/atomic.

Guess you like

Origin blog.csdn.net/weixin_44865158/article/details/115014306