Sync on the basis of Go language
rsync.WaitGroup
It is certainly inappropriate to use time.Sleep rigidly in the code. Sync. Waitgroup can be used in Go language to synchronize concurrent tasks. sync.WaitGroup has the following methods:
Method name | function |
---|---|
(wg * WaitGroup) Add(delta int) | Counter + delta |
(wg *WaitGroup) Done() | Counter-1 |
(wg *WaitGroup) Wait() | Blocks until the counter becomes 0 |
sync.WaitGroup internally maintains a counter whose value can be increased and decreased. For example, when we start N concurrent tasks, we increase the counter value by N. When each task is completed, the counter is decremented by 1 by calling the Done() method. Wait for the execution of concurrent tasks by calling Wait(). When the counter value is 0, it indicates that all concurrent tasks have been completed.
We use sync.WaitGroup to optimize the above code:
var wg sync.WaitGroup func hello() { defer wg.Done() fmt.Println("Hello Goroutine!") } func main() { wg.Add(1) go hello() // Start another goroutine to execute the hello function fmt.Println("main goroutine done!") wg.Wait() }
It should be noted that sync.WaitGroup is a structure, and the pointer should be passed when passing.
sync.Once
What I said earlier: 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 the configuration file only once, closing the channel only once, etc.
The sync package in the Go language provides a solution for a scenario that is 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 closures.
Load profile example
It is a good practice to delay an expensive initialization operation until it is actually used. Because initializing a variable in advance (such as completing initialization in init function) will increase the startup time of the program, and it may not be used in the actual execution process, the initialization operation is not necessary. Let's take an example:
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 is not concurrency safe when called by multiple goroutine s func Icon(name string) image.Image { if icons == nil { loadIcons() } return icons[name] }
When multiple goroutines call Icon functions concurrently, it is not concurrent and safe. Modern compilers and CPU s may freely rearrange the order of accessing memory on the basis of ensuring that each goroutine meets the serial consistency. The loadIcons function may be rearranged to the following results:
func loadIcons() { icons = make(map[string]image.Image) icons["left"] = loadIcon("left.png") icons["up"] = loadIcon("up.png") icons["right"] = loadIcon("right.png") icons["down"] = loadIcon("down.png") }
In this case, even if it is judged that the icons are not nil, it does not mean that the variable initialization is completed. Considering this situation, the way we can think of is to add mutexes to ensure that other goroutine operations will not be performed when initializing icons, but doing so will cause performance problems.
The example code of using sync.Once transformation 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 is concurrency safe func Icon(name string) image.Image { loadIconsOnce.Do(loadIcons) return icons[name] }
sync.Once actually contains a mutex and a Boolean value. The mutex ensures the security of Boolean values and data, and the Boolean value is used to record whether the initialization is completed. This design can ensure that the initialization operation is concurrent and safe, and the initialization operation will not be executed many times.
sync.Map
The built-in map in the Go language is not concurrency safe. See 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() }
There may be no problem when the above code starts a small number of goroutine s. When there is more concurrency, the above code will report a fatal error: concurrent map writes error.
In this scenario, it is necessary to lock the map to ensure concurrent security. The sync package of Go language provides a concurrent secure version of map - sync.Map out of the box. Out of the box means that you can use it directly without using the make function initialization like the built-in map. Meanwhile, sync.Map has built-in operation methods such as Store, Load, LoadOrStore, Delete and Range.
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() }