Using locks when we print errors may lead to unexpected results.
Let's look at the following example:
package main import ( "fmt" "sync" ) type Courseware struct { mutex Id int64 Code string Duration int } func (c *Courseware) UpdateDuration(duration int) error { () // 1 defer () if duration < 60 { return ("The courseware duration must be greater than or equal to 60 seconds: %v", c) // 2 } = duration return nil } // 3 func (c *Courseware) String() string { () defer () return ("id %d, duration %d", , ) } func main() { c := &Courseware{} ((0)) }
The above code seems to be fine, but it will cause deadlocks:
- Lock when updating the courseware to avoid data competition
- If the duration is less than 60 seconds, an error will be reported. But note that printing structure c here will call the String() method
- We look at the String method, and use a read lock to avoid data being updated during reading
Because critical resources are repeatedly locked, deadlock problems are caused. The solution is also very simple:
- Put the lock after the wrong judgment:
func (c *Courseware) UpdateDuration(duration int) error { if duration < 60 { return ("The courseware duration must be greater than or equal to 60 seconds: %v", c) // 2 } () defer () = duration return nil }
- Do not use the String method to avoid repeated locking:
package main import ( "fmt" "sync" ) type Courseware struct { mutex Id int64 Code string Duration int } func (c *Courseware) UpdateDuration(duration int) error { () defer () if duration < 60 { return ("The courseware duration must be greater than or equal to 60 seconds: %d, id: %d", , ) // Printing can also ensure safety by putting it in a lock } = duration return nil } func main() { c := &Courseware{} ((0)) }
go run The duration of the courseware must be greater than or equal to60Second: 0, id: 0
Let's look at another example of slices:
package main import ( "fmt" ) func main() { s := make([]int, 1) go func() { s1 := append(s, 1) (s1) }() go func() { s2 := append(s, 1) (s2) }() }
We initialize a slice with length 1 and capacity 1, and then call append in two coroutines to append elements to the slice. Will this situation lead to data competition?
The answer is no. In one of the coroutines, when we append the element, since the capacity of s is 1, the underlying layer will copy a new array; the same is true for the other coroutines.
go run -race [0 1] [0 1]
Note: The key here is whether the two coroutines will access a memory space at the same time, which is the key to data competition.
Let's modify the above example slightly:
package main import ( "fmt" ) func main() { s := make([]int, 1, 10) // 1 go func() { s1 := append(s, 1) (s1) }() go func() { s2 := append(s, 1) (s2) }() }
- We added a large enough capacity to S
go run -race [0 1] ================== WARNING: DATA RACE Write at 0x00c0000c0008 by goroutine 8: .func2() ...
You can see that this creates the problem of data competition. Because s has a large capacity, it is possible for two coroutines to operate the same piece of memory in the same underlying array.
The solution is also very simple, just copy an S again.
Let's continue to look at an example of a map:
package main import ( "strconv" "sync" "time" ) // 1 type User struct { mu online map[string]bool } // 2 func (u *User) AddOnline(id string) { () [id] = true () } // 3 func (u *User) AllOnline() int { () online := // 4 () sum := 0 for _, o := range online { // 5 if o { sum++ } } return sum } func main() { u := &User{} = make(map[string]bool) go func() { for i := 0; i < 10000; i++ { ("userid" + (i)) } }() go func() { for i := 0; i < 10000; i++ { () } }() () }
- We have a user organization, and there is an online field that is a map, which stores online user information.
- We have a method to add online users AddOnline. The lock is used in the method because map is concurrently unsafe.
- We also have a way to count all online users AllOnline
- In AllOnline, we access the map and we add a read lock. The idea here is to access the map of the current online user and assign it to the online, and then release the read lock
- traversing the assigned online to find out the number of online users
Maybe we think this is OK, but when we run the program, we will find that there is data competition here:
go run -race ================== WARNING: DATA RACE Write at 0x00c0000a0060 by goroutine 6: runtime.mapassign_faststr() ... ================== fatal error: concurrent map iteration and map write
This is because, inside the map, it is an hmap structure that mainly contains metadata (for example, counters) and pointers that reference the data bucket. therefore,online :=
The actual data will not be copied, but the copied pointer is still the same piece of memory.
It is not difficult to solve this problem:
- We can expand the range of the lock, like this:
func (u *User) AllOnline() int { () defer () online := sum := 0 for _, o := range online { if o { sum++ } } return sum }
- Another way is to copy a copy, like the slice we mentioned above:
func (u *User) AllOnline() int { () online := make(map[string]bool, len()) for s, b := range { online[s] = b } () sum := 0 for _, o := range online { if o { sum++ } } return sum }
In the above example, we used *User to define 2 methods:
func (u *User) AddOnline(id string) { () [id] = true () } func (u *User) AllOnline() int { () online := make(map[string]bool, len()) for s, b := range { online[s] = b } () sum := 0 for _, o := range online { if o { sum++ } } return sum }
Now let's modify the column above slightly:
package main import ( "strconv" "sync" "time" ) type User struct { mu online map[string]bool } func (u User) AddOnline(id string) { () [id] = true () } func (u User) AllOnline() int { () online := make(map[string]bool, len()) for s, b := range { online[s] = b } () sum := 0 for _, o := range online { if o { sum++ } } return sum } func main() { u := User{} = make(map[string]bool) go func() { for i := 0; i < 10000; i++ { ("userid" + (i)) } }() go func() { for i := 0; i < 10000; i++ { () } }() () }
Now we directly use the User structure to define these two methods, but when we execute the program, we report a data competition error:
go run -race ================== WARNING: DATA RACE Read at 0x00c00011e060 by goroutine 7: ()
What is the reason for this? This is because when our device uses User as a parameter, the copy of User is directly copied, and therefore it will also be copied.
Because the lock is copied, read and write operations in different locks can be accessed simultaneously for the same critical resource.
This is the end of this article about lock competition in golang. For more related go lock competition content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!