Struct is the key word we use when we write Go, but have you ever noticed that your program works when it comes to structs of a particular type?
A piece of code
type URL struct {
Ip string
Port string
mux sync.RWMutex
params url.Values
}
func (c *URL) Clone(a) URL {
newUrl := URL{}
newUrl.Ip = c.Ip
newUrl.params = url.Values{}
return newUrl
}
Copy the code
Can you see the problem in this code?
A: The program is normal B: Compilation fails C: panic D: Data race may occur E: Deadlock may occurCopy the code
If you can see what the problem is, then quietly tell you that this code is the underlying core code of a 3K Star Go framework on Github.
Say first conclusion
The above code problem is caused by sync.rwmutex. If you look at the introduction or source code for sync-related types, all types in the Sync package have a comment like this: ‘Must not be copied after first use’ may be copied from Mutex, WaitGroup or Cond I forgot all about that.
In my opinion, there are two reasons.
- Don’t know what sync variable replication is
- What happens when a variable of type sync is copied
The following examples use Mutex as an example
- The easiest thing to see
func main(a) {
var amux sync.Mutex
b := amux
b.Lock()
b.Unlock()
}
Copy the code
In this case, in general, no one uses it. No problem. Skip it
- Nested in structs, struct variables assigned to each other
type URL struct {
Ip string
Port string
mux sync.RWMutex
params url.Values
}
func main(a) {
var url1 URL
url2 := url1
}
Copy the code
When structs are nested with non-replicable types, you need to start being careful. When the struct nesting level is too deep or the struct variable spreads out as the value is passed, this becomes uncontrollable and requires special care.
- The value of a struct variable is passed as the return value
type URL struct {
Ip string
mux sync.RWMutex
}
func (c *URL) Clone(a) URL {
newUrl := URL{}
newUrl.Ip = c.Ip
return newUrl
}
Copy the code
- The value of a struct variable is passed as a receiver
type URL struct {
Ip string
mux sync.RWMutex
}
func (c URL) String(a) string {
c.paramsLock.Lock()
defer c.paramsLock.Unlock()
buf.WriteString(c.params.Encode())
return buf.String()
}
Copy the code
The result of replication
Example 1:
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
var age int
type Person struct {
mux sync.Mutex
}
func (p Person) AddAge(a) {
defer wg.Done()
p.mux.Lock()
age++
defer p.mux.Unlock()
}
func main(a) {
p1 := Person{
mux: sync.Mutex{},
}
wg.Add(100)
for i := 0; i < 100; i++ {
go p1.AddAge()
}
wg.Wait()
fmt.Println(age)
}
Copy the code
Result: It could be 100 or 99….
Example 2:
package main
import (
"fmt"
"sync"
)
type Person struct {
mux sync.Mutex
}
func Reduce(p Person) {
fmt.Println("step...", )
p.mux.Lock()
fmt.Println(p)
defer p.mux.Unlock()
fmt.Println("over...")}func main(a) {
var p Person
p.mux.Lock()
go Reduce(p)
p.mux.Unlock()
fmt.Println(111)
for{}}Copy the code
Result: Reduce coroutines are deadlocked.
As we can see here, when structs are nested with Mutex, if they are used in value-passing mode, they can cause program deadlocks, and variables that may need to be mutually exclusive are not mutually exclusive.
So either using variables of types that can’t be copied by themselves, or nested inside structs that can’t pass values.
Failure to replicate
Take Mutex for example,
type Mutex struct {
state int32
sema uint32
}
Copy the code
We use Mutex because different goroutines share a variable, so we need to make the variable mutually exclusive, otherwise the variables will be overwritten by each other. Mutex is controlled by state sema. When a Mutex variable is copied, the state of the Mutex, the state of the sema, is copied. This can result in either a deadlock in one of the Goroutines or a mismatch between variables shared by different Goroutines
How can structs be used with non-replicable types?
As you can see from the above, not only can sync-related type variables themselves not be copied, but sturct can also not be copied when nested with non-replicable type variables. But what if I change the nested non-copy-able variable to a pointer type variable, does that solve the non-copy-able problem?
type URL struct {
Ip string
mux *sync.RWMutex
}
Copy the code
This really solves the problem of not being able to copy. But that raises another question. As we all know, Go has no constructor. As a result, we need to initialize an RWMutex before we use a URL. Otherwise, we will have an equally serious null pointer problem.
How to copy a struct which contains a mutex? (This is just an example, there are many others), and I found that the view is basically the same, do not use struct to nest pointer type variables, therefore do not recommend that struct to nest non-copy pointer type variables. The most important reason: there is no tool to accurately detect null Pointers.
So in general, when a struct is nested with a variable of a non-replicable type, what you need to pass is a pointer to the struct variable.
How do you prevent copying variables that shouldn’t be copied?
Since Go does not provide the function of overloading, it is not possible to override the corresponding copied method of a struct. But the slot point for Go is that Go itself does not provide a strong compile-related constraint that it cannot be copied. It is possible that types that cannot be copied will be copied and muddled through. So what do we need to do?
Go is complemented by another tool, Go Vet, which detects whether non-replicable types have been copied.
func main(a) {
var amux sync.Mutex
b := amux
b.Lock()
b.Unlock()
}
Copy the code
$ go vet main.go
# command-line-arguments
./main.go:7:7: assignment copies lock value to b: sync.Mutex
Copy the code
How do we integrate Go Vet with everyday development?
- At present, Goland and Vscode will integrate related functions of Go Vet. If your OCD is serious, you can find relevant hints.
- In fact, it is more recommended to combine GO VET with THE CI process
golangci-lint
This Lint tool does CI
Go also provides a noCopy of code that can be added when your struct has a requirement that it cannot be copied
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock(a) {}
func (*noCopy) Unlock(a) {}
Copy the code
This code is still for go Vet.
Speaking of which, forbid copying variables that can’t be copied. Why make a tool to do this when you can do it at compile time? It’s a little confusing.
What are the types that cannot be copied?
The non-replicable types provided by Go are basically all the types in the Sync package: atomic.value, sync.mutex, sync.cond, sync.rwmutex, sync.map, sync.pool, sync.waitGroup.
These built-in non-replicable types can be found when copied with go Vet. But have you ever seen the following?
package main
import "fmt"
type Books struct {
someImportantData []int
}
func DoSomething(otherBook Books) Books {
newBook := otherBook
// do something
for k := range newBook.someImportantData {
newBook.someImportantData[k]++ // just like this
}
return otherBook
}
func main(a) {
oldBook := Books{
someImportantData: make([]int.0.100),
}
oldBook.someImportantData = append(oldBook.someImportantData, 1.2.3)
fmt.Println("before DoSomething, old book:", oldBook.someImportantData)
DoSomething(oldBook)
fmt.Println("after DoSomething, old book:", oldBook.someImportantData)
/ / using oldBook someImportantData continue to do things
}
Copy the code
Results:
before DoSomething, old book: [1 2 3]
after DoSomething, old book: [2 3 4]
Copy the code
The scene is actually we may inadvertently will encounter. OldBook is our operating data, but through DoSomething ` after oldBook. SomeImportantData value may be changed, that may not be what we are looking forward to. Because DoSomething is passed by copy, maybe we are not sensitive enough to pay attention to this point, so the logic of the program may be wrong. Can we set Books to not be copied? This allows Go Vet to help us identify these problems
At the end
Have you ever initialized WaitGroup like this?
wg := sync.WaitGroup{}
Copy the code
Is this a copy? Welcome to leave a comment.
Welcome to follow my public account: HHFCodeRv, follow my latest developments