In addition to the channel we introduced in the last section, there are also primitive synchronization mechanisms such as sync.Mutex and sync.WaitGroup to achieve more flexible data synchronization and control concurrency.
Competition for resources
Resource contention is when the same chunk of memory is accessed simultaneously by multiple goroutines in a program. Each goroutine has different operations on this shared resource (memory), which can lead to data clutter.
Example:
Package main import (" FMT ""time") var sum = 0 func main() {// Open 100 coroutine for sum + 1 for I := 1; i <= 100; Println("sum:",sum)} func add(){sum += 1} // Sum :98 or sum:99 or...
- Running the above program several times, we find that the printed result may be different, because we use multiple coroutines to manipulate SUM, and SUM is not concurrency safe, there is contention.
- When we use the Go Build, Go Run, and Go Test commands, we add the -race flag to check if there is a resource race in the code.
To solve this problem, we can lock the resource so that it can only be operated by one coroutine at a time.
sync.Mutex
- Mutex, so that only one coroutine can execute a certain program at the same time, other coroutines wait for the completion of the execution of the coroutine and then execute in turn.
- A mutex has only two methods: Lock and Unlock. When a coroutine locks a resource, the other coroutines cannot Lock it again until the coroutine has unlocked it.
- Lock and Unlock come in pairs. To prevent forgetting to release the Lock, release the Lock by using the DEFER statement.
Example:
Package main import (" FMT ""sync" "time") var sum = 0 var mutex = sync. mutex {} func main() {// Open 100 coroutine to make sum + 1 for i := 1; i <= 100; I++ {go add()} // Time.Sleep(2 * time.second) fmt.println ("sum:",sum)} func add(){mutex.lock () defer Mutex.unlock () // Use the defer statement to ensure that the lock is always released.
symc.RWMutex
- We used a mutex to prevent data error when multiple coroutines add sum at the same time. RWMutex is a read-write lock, so no matter how many goroutine reads are concurrency safe because the data does not change when a competing resource is read.
- Because multiple coroutines can be read at the same time, no longer waiting for each other, so in performance compared to the mutex will have a great improvement.
Example:
package main import ( "fmt" "sync" "time" ) var sum = 0 var mutex = sync.Mutex{} var rwmutex = sync.RWMutex{} func Main () {// start 100 coroutines to make sum + 1 for I := 1; i <= 100; i++ { go add() } for i := 1; i<= 10; I++ {go FMT.Println("sum:",getSum()))}} time.Sleep(2 * time. Sum)} func add(){mutex.lock () defer mutex.unlock () // use the defer statement, Sum += 1} func getSum() int {rwmutex.rlock () // defer rwmutex.runlock () return sum}
sync.WaitGroup
- Sleep(2 * time.Second) to prevent the mian function from returning and leaving the program prematurely. However, we don’t know when the program will actually finish executing, so we have to set it for a long time to avoid early exit, which can cause performance problems.
- This is where we use Sync.WaitGroup, which listens to the execution of the program and can exit once it has finished executing.
Example:
package main import ( "fmt" "sync" ) var sum = 0 var mutex = sync.Mutex{} var rwmutex = sync.RWMutex{} func run() { var Wg.Add(110) for I := 1; i <= 100; I ++ {go func() {defer wg.done () add()}()} for I := 1; i <= 10; I++ {go func () {/ / counter value minus 1 defer wg. Done (FMT). The Println (", "the sum, getSum ())} ()} / / has been waiting for, Wg.wait ()} func main() {run()} func add() {mutex.lock () defer mutex.unlock () // use the defer statement, Sum += 1} func getSum() int {rwmutex.rlock () // defer rwmutex.runlock () return sum}
- In the example, we start with the sync.waitGroup, and then set the value of the counter through the Add() method, which means how many coroutines are listening.
- After each coroutine completes, call the Done method to decrement the calculator by 1.
- Finally, the Wait method is called until the count is 0, so the coroutine is completely executed.
sync.Once
Sometimes we only want code to execute once, even in high-concurrency scenarios, such as creating a singleton. In this case, use sync.Once to ensure that the code is executed only Once.
Example:
package main import ( "fmt" "sync" ) func main() { var once sync.Once onceBody := func() { fmt.Println("Only once") } Do(onceBody) for I := 0; // for I := 0; i < 10; Do(onceBody) done < -true}()} for I := 0; // I := 0; // I := 0; i < 10; I++ {<-done}} // Only once
- The example above is native to the Go language. Although 10 coroutines are started to execute the onceBody function, the once.do method guarantees that onceBody will only be executed once.
- Sync. Once is good for scenarios where you need to execute only Once, such as creating singletons, loading resources only Once, and so on.
The condition variable sync.Cond
- We have a task that can only be performed if the conditions are met. Otherwise, we wait. So how do we get this condition? You can use a channel, but a channel works one to one, and one to many requires sync.Cond
- Sync. Cond is based on the mutex, adding a notification queue from which the notification coroutine wakes one or more notified coroutines.
- Sync. Cond has the following methods:
- Sync. NewCond(&mutex) // Sync. Cond is initialized with sync.NewCond and requires a mutex because it is based on sync. mutex to block waiting for notifications and to unblock notifications.
- Sync.wait () // waits for a notification to block the current coroutine until it is woken by another coroutine calling the Broadcast or Signal method, which requires a lock. Use the lock in sync.cond
- Sync.Signal() // Send a single notification to randomly wake up a coroutine
- Sync.broadcat () // Broadcast notifications to wake up all waiting coroutines.
Example:
Package main import (" FMT ""sync" "time") func main() {//3 people race, NewCond(&sync.mutex {}) var wg. WaitGroup wg.Add(4) //3 +1 for I := 1; i <= 3; i++ { go func(num int) { defer wg.Done() fmt.Println(num, "Runner number is in position ") cond.l.ock () cond.wait () fmt.println (num," Runner number has started to run... ") ) cond.l.unlock ()}(I)} // Wait time.Sleep(2 * time.second) go func() {defer wg.done (); Println(" Referee: "On your mark ~~ get ready ~~ ") FMT.Println(" Pap!! ") ) cond.Broadcast() // gun sounds}() // Prevent the function from returning early to exit wg.wait ()}
Running results:
No. 3 contestant is in position No. 1 contestant is in position No. 2 contestant is in position Referee: "on your mark ~~ get ready ~~" Pa!! Runner number two starts running... Runner number three starts running... Runner number one starts running...
The sync.Cond method is the sync.Cond method.
// Wait atomically unlocks c.L and suspends execution // of the calling goroutine. After later resuming execution, // Wait locks c.L before returning. Unlike in other systems, // Wait cannot return unless awoken by Broadcast or Signal. // Wait to release the lock and block coroutine execution. Once the condition is met to unblock, the current coroutine needs to acquire the lock and the Wait method returns. // // Because c.L is not locked when Wait first resumes, the caller // typically cannot assume that the condition is true when // Wait returns. Instead, The caller should Wait in a loop: // The caller should Wait in a loop: // The caller should Wait in a loop: // The caller should Wait in a loop. // c.L.Lock() // for ! condition() { // c.Wait() // } // ... make use of condition ... // c.l.lock () // func (c *Cond) Wait() {c.c.ker.check () t := runtime_notifyListAdd(&c.notify) c.l.lock () // Release the lock Runtime_notifyListWait (&c.notify, t) // Wait for conditions to be met, // Signal wakes one goroutine waiting on c, if there is any. // // It is allowed but not required for the caller to hold c.L // during the call. func (c *Cond) Signal() { c.checker.check() runtime_notifyListNotifyOne(&c.notify) } // Broadcast wakes all goroutines waiting on c. // // It is allowed but not required for the caller to hold c.L // during the call. func (c *Cond) Broadcast() { c.checker.check() runtime_notifyListNotifyAll(&c.notify) }
The condition variable’s Wait method does four main things:
- Add the calling goroutine (that is, the current goroutine) to the notification queue for the current condition variable.
- Unlocks the mutex on which the current condition variable is based.
- Leave the current goroutine in a wait state and wait until the notification arrives before deciding whether to wake it up. At this point, the goroutine will block on the line that called the Wait method.
- If the notification comes and you decide to wake up the goroutine, then re-lock the mutex on which the current condition variable is based after waking it. After that, the current goroutine continues to execute the rest of the code.
Matters needing attention
- Be sure to call wait with a lock on, otherwise the program will panic.
- When a wait is called, the goroutine is woken. This does not mean that the goroutine is woken. The wait is simply given a chance to check.
// c.L.Lock() // for ! condition() { // c.Wait() // } // ... make use of condition ... // c.L.Unlock()
- The Signal and BoardCast wake operations do not require a lock
sync.Map
Map reading and writing at the same time is thread-unsafe and can cause race problems. Sync. Map, on the other hand, is the same as the Map type, except that it is concurrency safe. The sync.Map method:
- Store: Store key-value values
- Load: Obtain the corresponding value value according to the key, and can also judge whether the key exists.
- LoadorStore: returns value if the value corresponding to key exists; Store the key-value if it does not exist.
- Delete: Deletes a key-value pair
- Range: traverse sync.Map
Example:
Package main import (" FMT ""sync") func main() {var syMap sync.Map // Save key and value pairs to sync.Map symap. Store("aaa"), 111) syMap.Store("bbb", 222) syMap.Store("ccc", 333) fmt.Println(syMap.LoadOrStore("ddd", Symap.load ("aaa")) // Delete the corresponding key-value pair symap.delete ("aaa") // Traverse all the key-value pairs in sync.Map Symap.range (func(k, v interface{}) bool {fmt.println ("k:", k, "= "v:", v) return true})}
Running results:
444 false
111 true
k: bbb =》 v: 222
k: ccc =》 v: 333
k: ddd =》 v: 444
Sync.map does not have a way to get the number of maps, so you can calculate the number as you traverse it. Sync.map sacrifices some performance for concurrency security, so if there are no concurrency scenarios, it is recommended to use the built-in Map class.