preface
Go is a simple and interesting programming language, and like any other language, there are many pitfalls to use, but most of them are not design flaws in Go itself. If you’ve just switched to Go from another language, chances are you’ll step on the toes of this article.
If you take the time to learn about official doc, wiki, discussion mailing lists, Rob Pike’s numerous articles, and Go’s source code, you’ll find that the pits in this article are very common, and novice users can save a lot of time debugging code by skipping them.
Chapter 1:1-34
1. Open curly braces{
Generally cannot put a single line
In most other languages, the position of {is up to you. Go is unique in that it follows the automatic semicolon injection rule: the compiler adds a certain separator at the end of each line of code. To separate multiple statements, for example by adding a semicolon after) :
// Error examples
func main(a)
{
println("hello world")}/ / equivalent
func main(a); // No function body
{
println("hello world")}Copy the code
./main.go: missing function body ./main.go: syntax error: unexpected semicolon or newline before {
// Correct example
func main(a) {
println("hello world")}Copy the code
Note special cases such as code blocks:
// {if the semicolon injection rule is not followed, it will not be added automatically
func main(a){{println("hello world")}}Copy the code
Reference: Special separator for automatically adding semicolons in Golang
2. Unused variables
If there are unused variables in the function body code, it will not compile, but it is ok to declare global variables without using them.
Even if you assign a value to a variable after it is declared, it still won’t compile, so you need to use it somewhere:
// Error examples
var gvar int // Global variables can be declared without use
func main(a) {
var one int // error: one declared and not used
two := 2 // error: two declared and not used
var three int // error: three declared and not used
three = 3
}
// Correct example
// You can comment or remove unused variables directly
func main(a) {
var one int
_ = one
two := 2
println(two)
var three int
one = three
var four int
four = four
}
Copy the code
3. Unused import
If you import a package but none of the variables, functions, interfaces, and structures in the package are used, the compilation will fail.
You can avoid compilation errors by ignoring imported packages by using the _ _ _ symbol as an alias, which only executes the package’s init()
// Error examples
import (
"fmt" // imported and not used: "fmt"
"log" // imported and not used: "log"
"time" // imported and not used: "time"
)
func main(a){}// Correct example
// You can use the Goimports tool to comment or remove unused packages
import(_"fmt"
"log"
"time"
)
func main(a) {
_ = log.Println
_ = time.Now
}
Copy the code
4. Short declared variables can only be used inside functions
// Error examples
myvar := 1 // syntax error: non-declaration statement outside function body
func main(a){}// Correct example
var myvar = 1
func main(a){}Copy the code
5. Repeat variables with short declarations
If there is at least one new variable to the left of :=, multiple variable declarations are allowed:
// Error examples
func main(a) {
one := 0
one := 1 // error: no new variables on left side of :=
}
// Correct example
func main(a) {
one := 0
one, two := 1.2 // two is a new variable, allowing repeated declarations of one. For example, error processing often uses the same name variable err
one, two = two, one // Swap the shorthand for the values of the two variables
}
Copy the code
6. You cannot use a short declaration to set the value of a field
Struct variable fields cannot be assigned using := to use predefined variables to avoid resolution:
// Error examples
type info struct {
result int
}
func work(a) (int, error) {
return 3.nil
}
func main(a) {
var data info
data.result, err := work() // error: non-name data.result on left side of :=
fmt.Printf("info: %+v\n", data)
}
// Correct example
func main(a) {
var data info
var err error // Err requires pre-declaration
data.result, err = work()
iferr ! =nil {
fmt.Println(err)
return
}
fmt.Printf("info: %+v\n", data)
}
Copy the code
7. Accidentally overwriting variables
For developers moving from dynamic languages, short declarations are useful, which can lead to misunderstandings := is an assignment operator.
If you misuse a new block of code like :=, the compile will not report an error, but the variable will not work as you expect:
func main(a) {
x := 1
println(x) / / 1
{
println(x) / / 1
x := 2
println(x) // the new x variable is scoped only within the code block
}
println(x) / / 1
}
Copy the code
This is a common mistake that Go developers make, and it’s not easy to catch.
You can use the VET tool to diagnose this variable override. Go does not do the override check by default. Add the -shadow option to enable it:
> go tool vet -shadow main.go
main.go:9: declaration of "x" shadows declaration at main.go:5
Copy the code
Note that VET does not report all covered variables, you can use GO-Nyet for further testing:
> $GOPATH/bin/go-nyet main.go
main.go:10:3:Shadowing variable `x`
Copy the code
Variables of explicit type cannot be initialized using nil
Nil is the default initial value for variables of type interface, function, Pointer, Map, slice, and channel. However, the declaration does not specify a type, and the compiler cannot infer the specific type of the variable.
// Error examples
func main(a) {
var x = nil // error: use of untyped nil
_ = x
}
// Correct example
func main(a) {
var x interface{} = nil
_ = x
}
Copy the code
9. Use slice and Map with nil values
Adding elements to a nil slice is allowed, but adding elements to a nil map causes runtime panic
// Map error example
func main(a) {
var m map[string]int
m["one"] = 1 // error: panic: assignment to entry in nil map
// m := make(map[string]int)// make(map[string]int
}
// Correct example of slice
func main(a) {
var s []int
s = append(s, 1)}Copy the code
The capacity of the map
You can specify the size when creating a variable of type Map, but you can’t use cap() to detect the size of the allocated space as you can with slice:
// Error examples
func main(a) {
m := make(map[string]int.99)
println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap
}
Copy the code
11. Variables of type string cannot be nil
For those of you who like to initialize strings in nil, this is a pit:
// Error examples
func main(a) {
var s string = nil // cannot use nil as type string in assignment
if s == nil { // invalid operation: s == nil (mismatched types string and nil)
s = "default"}}// Correct example
func main(a) {
var s string // The null value of the string type is an empty string ""
if s == "" {
s = "default"}}Copy the code
12. The value of the Array type is used as the function parameter
In C/C++, arrays (names) are Pointers. When you pass an array as an argument to a function, you pass a reference to the memory address of the array, which changes the value of the array inside the function.
In Go, arrays are values. When passed as an argument to a function, a copy of the original value of the array is passed, and the array cannot be updated inside the function:
// The array uses value copy to pass parameters
func main(a) {
x := [3]int{1.2.3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) / / [2, 3, 7]
}(x)
fmt.Println(x) // [1 2 3] // It's not what you think.
}
Copy the code
If you want to modify the parameter array:
- Pass the type of pointer to the array directly:
// The address changes the original data
func main(a) {
x := [3]int{1.2.3}
func(arr *[3]int) {
(*arr)[0] = 7
fmt.Println(arr) / / & [2, 3, 7]
}(&x)
fmt.Println(x) / / [2, 3, 7]
}
Copy the code
- Use slice directly: the original slice data (the underlying array) is updated even if the function internally obtains a copy of the slice value.
// Modify slice by modifying the array underlying slice
func main(a) {
x := []int{1.2.3}
func(arr []int) {
arr[0] = 7
fmt.Println(x) / / [2, 3, 7]
}(x)
fmt.Println(x) / / [2, 3, 7]
}
Copy the code
13. Range confused return values when traversing Slice and array
Unlike the for-in and foreach traversal statements in other programming languages, the range in Go generates two values during traversal, the first is the element index and the second is the element value:
// Error examples
func main(a) {
x := []string{"a"."b"."c"}
for v := range x {
fmt.Println(v) / / 1 2 3}}// Correct example
func main(a) {
x := []string{"a"."b"."c"}
for _, v := range x { // Use _ to discard the index
fmt.Println(v)
}
}
Copy the code
14. Slice and array are one-dimensional data
It looks like Go supports multidimensional arrays and slices, creating arrays of arrays and slices of slices, but it doesn’t.
For applications that rely on dynamically calculating multidimensional array values, Go is not ideal in terms of performance and complexity.
Dynamic multidimensional arrays can be created using original one-dimensional arrays, “independent” slices, and slices that “share the underlying array.”
-
Use the original one-dimensional array: do index checking, overflow detection, and re-allocate memory when the array is full.
-
Use “stand-alone” slices in two steps:
-
Creating an external Slice
-
Memory is allocated for each internal slice
Note that the internal slices are independent of each other, so that any increase or decrease in an internal slice does not affect any other slice
-
// Create a dynamic multidimensional array for [2][3] using six separate slices
func main(a) {
x := 2
y := 4
table := make([] []int, x)
for i := range table {
table[i] = make([]int, y)
}
}
Copy the code
- Use a slice of “Share underlying array”
-
Create a container slice to hold the raw data
-
Create another slice
-
Initialize other slices by cutting the original slice
func main(a) {
h, w := 2.4
raw := make([]int, h*w)
for i := range raw {
raw[i] = i
}
// Initialize raw slice
fmt.Println(raw, &raw[4]) // [0 1 2 3 4 5 6 7] 0xc420012120
table := make([] []int, h)
for i := range table {
// Create a dynamic multidimensional array table by cutting the original slice with equal spacing
// 0: raw[0*4: 0*4 + 4]
// 1: raw[1*4: 1*4 + 4]
table[i] = raw[i*w : i*w + w]
}
fmt.Println(table, &table[1] [0]) // [[0 1 2 3] [4 5 6 7]] 0xc420012120
}
Copy the code
More references on multidimensional arrays
go-how-is-two-dimensional-arrays-memory-representation
what-is-a-concise-way-to-create-a-2d-slice-in-go
15. Access the key that does not exist in the map
Like any other programming language, you want to return nil if you access a key that doesn’t exist in the map, such as in PHP:
> php -r '$v = ["x"=>1, "y"=>2]; @var_dump($v["z"]); '
NULL
Copy the code
Go returns the zero value of the corresponding data type of the element, such as nil, “”, false, and 0. The value operation always returns a value, so it is not possible to determine whether the key is in the map by retrieving the value.
To check whether the key is accessible directly from the map, check the second argument returned:
// Incorrect key detection mode
func main(a) {
x := map[string]string{"one": "2"."two": ""."three": "3"}
if v := x["two"]; v == "" {
fmt.Println("key two is no entry") // The two key returns an empty string if it does not exist}}// Correct example
func main(a) {
x := map[string]string{"one": "2"."two": ""."three": "3"}
if _, ok := x["two"]; ! ok { fmt.Println("key two is no entry")}}Copy the code
16. Values of type string are constants and cannot be changed
Attempts to use index traversal to update individual characters in the string are not allowed.
The value of string is a read-only binary byte slice. If you want to change the characters in the string, convert the string to []byte and then convert it to string:
// An example of an error in modifying a string
func main(a) {
x := "text"
x[0] = "T" // error: cannot assign to x[0]
fmt.Println(x)
}
// Modify the example
func main(a) {
x := "text"
xBytes := []byte(x)
xBytes[0] = 'T' // Note that T is of type rune
x = string(xBytes)
fmt.Println(x) // Text
}
Copy the code
Note: The above example is not the correct way to update a string, because a UTF8 encoded character can be multiple bytes, such as a Chinese character that requires 3-4 bytes to store, and it is wrong to update one byte.
Correct posture for updating a string: convert a string into a Rune slice (1 Rune may be multiple bytes), directly updating the characters in the rune
func main(a) {
x := "text"
xRunes := []rune(x)
xRunes[0] = '我'
x = string(xRunes)
fmt.Println(x) / / I am ext
}
Copy the code
17. Conversion between string and Byte slice
When a string and byte slice are converted to each other, the original value of the copy is converted. This conversion process is different from casting operations in other programming languages, and from the fact that the new slice shares the underlying array with the old slice.
Go optimizes the conversion between string and byte slice in two ways to avoid extra memory allocation:
- in
map[string]
Is used to find the key in[]byte
To avoid doingm[string(key)]
Memory allocation for - use
for range
Iterating a string to an iteration of []byte:for i,v := range []byte(str) {... }
Fog: reference
18. String and index operators
Index access to a string returns not a character, but a byte value.
This is handled in the same way as in other languages, such as PHP:
> php -r '$name = "Chinese"; var_dump($name); ' # "Chinese" takes 6 bytesString (6) "中文"
> php -r '$name = "Chinese"; var_dump($name[0]); ' # Reads the first byte as a Unicode character and displays U+FFFDString (1) "�"
> php -r '$name = "Chinese"; var_dump($name[0].$name[1].$name[2]); 'String (3) "in"Copy the code
func main(a) {
x := "ascii"
fmt.Println(x[0]) / / 97
fmt.Printf("%T\n", x[0])// uint8
}
Copy the code
If you need to use for range to iterate over characters in a string (Unicode Code point/rune), the library has the “Unicode/UTF8” package to do the related decoding for UTF8. Utf8string also has library functions like func (s *String) At(I int) rune.
19. Not all strings are UTF8 text
The value of string does not have to be UTF8 text and can contain arbitrary values. The string is UTF8 text only if it is a literal literal, and the string can be escaped to contain other data.
To determine if a string is UTF8 text, use the ValidString() function in the “Unicode/UTF8” package:
func main(a) {
str1 := "ABC"
fmt.Println(utf8.ValidString(str1)) // true
str2 := "A\xfeC"
fmt.Println(utf8.ValidString(str2)) // false
str3 := "A\\xfeC"
fmt.Println(utf8.ValidString(str3)) // escape the escaped character to a literal
}
Copy the code
20. The length of the string
In Python:
data = U 'has'
print(len(data)) # 1
Copy the code
In Go, however:
func main(a) {
char := "Has"
fmt.Println(len(char)) / / 3
}
Copy the code
Go’s built-in function len() returns the number of bytes in the string, not Unicode characters as in Python.
To get the number of characters in the string, use RuneCountInString(STR string) (n int) in the “Unicode/UTF8” package
func main(a) {
char := "Has"
fmt.Println(utf8.RuneCountInString(char)) / / 1
}
Copy the code
Note: RuneCountInString does not always return the number of characters we see, because some characters take up two rune:
func main(a) {
char := "é"
fmt.Println(len(char)) / / 3
fmt.Println(utf8.RuneCountInString(char)) / / 2
fmt.Println("cafe\u0301") // Cafe // French cafe is actually a combination of two rune
}
Copy the code
Reference: normalization
21. The multi-line array, slice, and map statements are missing.
号
func main(a) {
x := []int {
1.2 // syntax error: unexpected newline, expecting comma or }
}
y := []int{1.2,}
z := []int{1.2}
// ...
}
Copy the code
Declaratory statements in which the} collapses into a single line, trailing, are not required.
22. log.Fatal
和 log.Panic
Not only the log
The log library provides different logging levels. Unlike other language logging libraries, the Go log package can do more than log, such as interrupt program execution, when calling Fatal*() and Panic*() :
func main(a) {
log.Fatal("Fatal level log: log entry") // After the output, the program terminates execution
log.Println("Nomal level log: log entry")}Copy the code
23. Operations on built-in data structures are not synchronous
Although Go itself has a large number of features to support concurrency, it does not guarantee the security of concurrent data. Users need to ensure that data such as variables are updated by atomic operation.
Goroutine and channel are a good way to do atomic operations, or to use locks in the “sync” package.
24. Range Iterates the string value
The index obtained by range is the position of the first byte of the character value (Unicode Point/RUNe). Unlike other programming languages, this index is not directly the position of the character in the string.
Note that one character can have more than one rune, such as the e in the French word cafe. You can manipulate special characters using the Norm package.
The for range iteration attempts to translate the string into UTF8 text, using the 0XFFFD RUNe (�) UNicode replacement character directly for any invalid code points. If there is any data in the string that is not UTF8, the string should be saved as a byte slice before operation.
func main(a) {
data := "A\xfe\x02\xff\x04"
for _, v := range data {
fmt.Printf("%#x ", v) // 0x41 0xFFFD 0x2 0xFFFD 0x4 // Error
}
for _, v := range []byte(data) {
fmt.Printf("%#x ", v) 0x41 0xFe 0x2 0xFF 0x4 // Correct}}Copy the code
25. Range Iterates the map
If you want to iterate over the map in a particular order (such as by key), be aware that each iteration may produce different results.
The Go runtime intentionally scrambles the iteration order, so you may get inconsistent iteration results. However, it is not always disrupted, and it is possible to get the same results of five consecutive iterations, such as:
func main(a) {
m := map[string]int{"one": 1."two": 2."three": 3."four": 4}
for k, v := range m {
fmt.Println(k, v)
}
}
Copy the code
If you run the above code repeatedly on the Go Playground, the output won’t change, and it will only recompile if you update the code. The iteration order is scrambled after recompilation:
26. Fallthrough statements in the switch
Case blocks in a switch statement are broken by default, but fallthrough can be used to force the next case block to execute.
func main(a) {
isSpace := func(char byte) bool {
switch char {
case ' ': // The space is broken directly, returning false // unlike other languages
Fallthrough // Returns true
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) // true
fmt.Println(isSpace(' ')) // false
}
Copy the code
However, you can use fallthrough at the end of a case block to enforce the next case block.
Case:
func main(a) {
isSpace := func(char byte) bool {
switch char {
case ' '.'\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) // true
fmt.Println(isSpace(' ')) // true
}
Copy the code
27. Increment and decrement operations
Many programming languages come with pre – and post – + and – operations. Go, however, is unique in that it does away with the preposition and uses ++ and – as operators rather than expressions.
// Error examples
func main(a) {
data := []int{1.2.3}
i := 0
++i // syntax error: unexpected ++, expecting }
fmt.Println(data[i++]) // syntax error: unexpected ++, expecting :
}
// Correct example
func main(a) {
data := []int{1.2.3}
i := 0
i++
fmt.Println(data[i]) / / 2
}
Copy the code
28. Reverse bitwise
Many programming languages use ~ as the unary bitwise invert (NOT) operator, and Go reuses the ^ XOR operator to bitwise invert:
// Incorrect fetch operation
func main(a) {
fmt.Println(~2) // bitwise complement operator is ^
}
// Correct example
func main(a) {
var d uint8 = 2
fmt.Printf("%08b\n", d) / / 00000010
fmt.Printf("%08b\n", ^d) / / 11111101
}
Copy the code
^ is also the bitwise XOR operator.
An operator can be reused twice because the unary NOT operation NOT 0x02 is consistent with the binary XOR operation 0x22 XOR 0xFF.
Go also has a special AND NOT &^ operator that takes 1 for different bits.
func main(a) {
var a uint8 = 0x82
var b uint8 = 0x02
fmt.Printf("%08b [A]\n", a)
fmt.Printf("%08b [B]\n", b)
fmt.Printf("%08b (NOT B)\n", ^b)
fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n", b, 0xff, b^0xff)
fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n", a, b, a^b)
fmt.Printf("%08b & %08b = %08b [A AND B]\n", a, b, a&b)
fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n", a, b, a&^b)
fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n", a, b, a&(^b))
}
Copy the code
10000010 [A] 00000010 [B] 11111101 (NOT B) 00000010 ^ 11111111 = 11111101 [B XOR 0xff] 10000010 ^ 00000010 = 10000000 [A XOR B] 10000010 & 00000010 = 00000010 [A AND B] 10000010 &^00000010 = 10000000 [A 'AND NOT' B] 10000010&(^00000010)= 10000000 [A AND (NOT B)]Copy the code
29. Precedence of operators
In addition to the bit clear operator, Go has many of the same bit operators as other languages, but priority is another matter.
func main(a) {
fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n".0x2&0x2+0x4) // & + is preferred
//prints: 0x2 & 0x2 + 0x4 -> 0x6
//Go: (0x2 & 0x2) + 0x4
//C++: 0x2 & (0x2 + 0x4) -> 0x2
fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n".0x2+0x2<<0x1) // << priority +
//prints: 0x2 + 0x2 << 0x1 -> 0x6
//Go: 0x2 + (0x2 << 0x1)
//C++: (0x2 + 0x2) << 0x1 -> 0x8
fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n".0xf|0x2^0x2) / / | priority ^
//prints: 0xf | 0x2 ^ 0x2 -> 0xd
//Go: (0xf | 0x2) ^ 0x2
//C++: 0xf | (0x2 ^ 0x2) -> 0xf
}
Copy the code
Priority list:
Precedence Operator
5 * / % << >> & &^
4 + - | ^
3= =! = < <= > >=2 &&
1 ||
Copy the code
30. Unexported struct fields cannot be encode
Field members starting with a lowercase letter cannot be directly accessed externally, so struct will ignore these private fields during encode operations in JSON, XML, GOb and other formats, and will get zero value when exported:
func main(a) {
in := MyData{1."two"}
fmt.Printf("%#v\n", in) // main.MyData{One:1, two:"two"}
encoded, _ := json.Marshal(in)
fmt.Println(string(encoded)) // {"One":1} // Private field two is ignored
var out MyData
json.Unmarshal(encoded, &out)
fmt.Printf("%#v\n", out) // main.MyData{One:1, two:""}
}
Copy the code
31. The program exits with the Goroutine still running
By default, the program does not exit until all goroutines have been executed. This is important to note:
// The main program exits directly
func main(a) {
workerCount := 2
for i := 0; i < workerCount; i++ {
go doIt(i)
}
time.Sleep(1 * time.Second)
fmt.Println("all done!")}func doIt(workerID int) {
fmt.Printf("[%v] is running\n", workerID)
time.Sleep(3 * time.Second) // Simulation goroutine is executing
fmt.Printf("[%v] is done\n", workerID)
}
Copy the code
The main program exits before two Goroutines have finished executing:
Common solution: Use the “WaitGroup” variable, which makes the main program wait for all goroutines to execute before exiting.
If your Goroutine wants to do time-consuming things like loop processing of messages, send them a kill message to shut them down. Or just close a channel where they are both waiting to receive data:
// Wait for all goroutines to complete
// Enter the deadlock
func main(a) {
var wg sync.WaitGroup
done := make(chan struct{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doIt(i, done, wg)
}
close(done)
wg.Wait()
fmt.Println("all done!")}func doIt(workerID int, done <-chan struct{}, wg sync.WaitGroup) {
fmt.Printf("[%v] is running\n", workerID)
defer wg.Done()
<-done
fmt.Printf("[%v] is done\n", workerID)
}
Copy the code
Execution Result:
It looks as if the goroutine has been executed, but an error is reported:
fatal error: all goroutines are asleep – deadlock!
Why do deadlocks occur? Goroutine called wg.done () before exiting, the program should exit normally.
The reason is that the “WaitGroup” variable goroutine gets is a copy of the var WG WaitGroup value, that is, doIt() passes only the value. So even if warg.done () is called in each goroutine, wg variables in the main program will not be affected.
// Wait for all goroutines to complete
// Pass parameters to the WaitGroup variable using the addressing mode
// Close the Goroutine with channel
func main(a) {
var wg sync.WaitGroup
done := make(chan struct{})
ch := make(chan interface{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doIt(i, ch, done, &wg) // Wargaming passes Pointers, doIt() internally changes the wargaming value
}
for i := 0; i < workerCount; i++ { // Send data to ch, close goroutine
ch <- i
}
close(done)
wg.Wait()
close(ch)
fmt.Println("all done!")}func doIt(workerID int, ch <-chan interface{}, done <-chan struct{}, wg *sync.WaitGroup) {
fmt.Printf("[%v] is running\n", workerID)
defer wg.Done()
for {
select {
case m := <-ch:
fmt.Printf("[%v] m => %v\n", workerID, m)
case <-done:
fmt.Printf("[%v] is done\n", workerID)
return}}}Copy the code
Operation effect:
32. Send data to unbuffered channels and return as soon as the receiver is ready
The sender will only block if the data is being processed by the receiver. Depending on the runtime environment, the Receiver’s Goroutine may not have enough time to process the next piece of data after the sender has sent it. Such as:
func main(a) {
ch := make(chan string)
go func(a) {
for m := range ch {
fmt.Println("Processed:", m)
time.Sleep(1 * time.Second) // Simulate operations that need to run for a long time
}
}()
ch <- "cmd.1"
ch <- "cmd.2" // Will not be processed
}
Copy the code
Operation effect:
33. Sending data to a closed channel will cause a panic
It is safe to receive data from a closed channel:
When ok is false, there is no data available to receive in the channel. Similarly, when receiving data from a buffered channel, the status value is also false when the cached data has been retrieved and no more data is available
Sending data to a closed channel causes a panic:
func main(a) {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- idx
}(i)
}
fmt.Println(<-ch) // Output the first value sent
close(ch) // Can't close, there are other sender
time.Sleep(2 * time.Second) // Simulate to do other operations
}
Copy the code
Running result:
For the buggy example above, you can use a discarded channel done to tell the remaining Goroutines that they no longer need to send data to the CH. < -done = {};
func main(a) {
ch := make(chan int)
done := make(chan struct{})
for i := 0; i < 3; i++ {
go func(idx int) {
select {
case ch <- (idx + 1) * 2:
fmt.Println(idx, "Send result")
case <-done:
fmt.Println(idx, "Exiting")
}
}(i)
}
fmt.Println("Result: ", <-ch)
close(done)
time.Sleep(3 * time.Second)
}
Copy the code
Operation effect:
34. The value ofnil
The channel
Sending and receiving data on a nil channel will permanently block:
func main(a) {
var ch chan int // Uninitialized, value nil
for i := 0; i < 3; i++ {
go func(i int) {
ch <- i
}(i)
}
fmt.Println("Result: ", <-ch)
time.Sleep(2 * time.Second)
}
Copy the code
Runtime deadlock error:
fatal error: all goroutines are asleep – deadlock! goroutine 1 [chan receive (nil chan)]
This deadlock feature can be used to dynamically open and close case blocks in select:
func main(a) {
inCh := make(chan int)
outCh := make(chan int)
go func(a) {
var in <-chan int = inCh
var out chan<- int
var val int
for {
select {
case out <- val:
println("-- -- -- -- -- -- -- --")
out = nil
in = inCh
case val = <-in:
println("+ + + + + + + + + +")
out = outCh
in = nil}}} ()go func(a) {
for r := range outCh {
fmt.Println("Result: ", r)
}
}()
time.Sleep(0)
inCh <- 1
inCh <- 2
time.Sleep(3 * time.Second)
}
Copy the code
Operation effect:
34. If the parameter passing mode of function receiver is value passing, the original value of the parameter cannot be modified
The parameters of the method Receiver are similar to the parameters of a normal function: if declared as a value, the method body gets a copy of the value of the parameter, and any changes to the parameter have no effect on the original value.
Unless the receiver parameter is a map or slice variable and is a pointer to a field in map or an element in slice, the value will be updated:
type data struct {
num int
key *string
items map[string]bool
}
func (this *data) pointerFunc(a) {
this.num = 7
}
func (this data) valueFunc(a) {
this.num = 8
*this.key = "valueFunc.key"
this.items["valueFunc"] = true
}
func main(a) {
key := "key1"
d := data{1, &key, make(map[string]bool)}
fmt.Printf("num=%v key=%v items=%v\n", d.num, *d.key, d.items)
d.pointerFunc() // Change the num value to 7
fmt.Printf("num=%v key=%v items=%v\n", d.num, *d.key, d.items)
d.valueFunc() // Modify the key and items values
fmt.Printf("num=%v key=%v items=%v\n", d.num, *d.key, d.items)
}
Copy the code
Running result:
Intermediate: 35-50
35. Close the HTTP response body
When you make a request and get a response using the HTTP standard library, you need to manually close the response body, even if you don’t read any data from the response or the response is empty. It’s easy for beginners to forget to turn it off manually, or write it in the wrong place:
// The request failed, causing a panic
func main(a) {
resp, err := http.Get("https://api.ipify.org?format=json")
defer resp.Body.Close() // Resp may be nil and cannot read the Body
iferr ! =nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
func checkError(err error) {
iferr ! =nil{
log.Fatalln(err)
}
}
Copy the code
The above code makes the request correctly, but if the request fails, the variable resp is nil, causing a panic:
panic: runtime error: invalid memory address or nil pointer dereference
You should first check that the HTTP response error is nil and then call resp.body.close () to Close the response Body:
// A correct example in most cases
func main(a) {
resp, err := http.Get("https://api.ipify.org?format=json")
checkError(err)
defer resp.Body.Close() // The correct way to close in most cases
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
Copy the code
Output:
Get api.ipify.org?format=json: x509: certificate signed by unknown authority
In most cases of request failure, resP is nil and err is non-nil. But if you get a redirection error, and both of them are non-nil, you can still end up with a memory leak. Two solutions:
- You can close the non-nil response body directly in the code block that handles HTTP response errors.
- Manual call
defer
To close the response body:
// Correct example
func main(a) {
resp, err := http.Get("http://www.baidu.com")
// Close the correct posture for resp.body
ifresp ! =nil {
defer resp.Body.Close()
}
checkError(err)
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
Copy the code
Earlier versions of resp.body.close () were implemented to read the response Body and then discard it, ensuring that keep-alive HTTP connections can reuse more than one request. However, the latest version of Go leaves the task of reading and discarding data to the user. If you don’t handle this, HTTP connections can be closed rather than reused, as described in the Go version 1.5 documentation.
If your program reuses a lot of HTTP persistent connections, you might want to include in the logic that handles the response:
_, err = io.Copy(ioutil.Discard, resp.Body) // Manually discard the read data
Copy the code
The above code needs to be written if you need to read the response in its entirety. For example, in decoding API JSON response data:
json.NewDecoder(resp.Body).Decode(&data)
Copy the code
36. Close the HTTP connection
Some servers that support HTTP1.1 or HTTP1.0 and have the Connection: keep-alive option configured maintain a long connection for a period of time. By default, however, the library “NET/HTTP” connections are only disconnected when the server asks to be shut down, so your program may run out of socket descriptors. There are two solutions, after the request is completed:
- Set the request variable directly
Close
The field values fortrue
The connection will be closed after each request. - Set Header request Header options
Connection: close
Then the response header returned by the server will also have this option, and the HTTP library will actively disconnect.
// Close the connection voluntarily
func main(a) {
req, err := http.NewRequest("GET"."http://golang.org".nil)
checkError(err)
req.Close = true
// req.header. Add("Connection", "close") // Equivalent close mode
resp, err := http.DefaultClient.Do(req)
ifresp ! =nil {
defer resp.Body.Close()
}
checkError(err)
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
Copy the code
You can create a custom configured HTTP Transport client to unreuse HTTP global connections:
func main(a) {
tr := http.Transport{DisableKeepAlives: true}
client := http.Client{Transport: &tr}
resp, err := client.Get("https://golang.google.cn/")
ifresp ! =nil {
defer resp.Body.Close()
}
checkError(err)
fmt.Println(resp.StatusCode) / / 200
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(len(string(body)))
}
Copy the code
Select the following scenarios as required:
-
If your application sends a large number of requests to the same server, use the default keep-alive connection.
-
If your application is connecting to a large number of servers and each server requests only once or twice, close the connection when you receive the request. Or increase the value of the maximum number of open files fs.file-max.
37. Decode the numbers in JSON to the interface type
When encode/decode JSON data, Go defaults to treating a value as a float64 value. For example, the following code will cause panic:
func main(a) {
var data = []byte(`{"status": 200}`)
var result map[string]interface{}
iferr := json.Unmarshal(data, &result); err ! =nil {
log.Fatalln(err)
}
fmt.Printf("%T\n", result["status"]) // float64
var status = result["status"]. (int) // Type assertion error
fmt.Println("Status value: ", status)
}
Copy the code
panic: interface conversion: interface {} is float64, not int
If you try to decode a JSON field with an integer, you can:
-
Convert int value to float
-
Convert the float value required after decode to an int
// Convert the decode value to int
func main(a) {
var data = []byte(`{"status": 200}`)
var result map[string]interface{}
iferr := json.Unmarshal(data, &result); err ! =nil {
log.Fatalln(err)
}
var status = uint64(result["status"]. (float64))
fmt.Println("Status value: ", status)
}
Copy the code
- use
Decoder
Type to decode JSON data, explicitly indicating the value type of the field
// Specify the field type
func main(a) {
var data = []byte(`{"status": 200}`)
var result map[string]interface{}
var decoder = json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
iferr := decoder.Decode(&result); err ! =nil {
log.Fatalln(err)
}
var status, _ = result["status"].(json.Number).Int64()
fmt.Println("Status value: ", status)
}
// You can use string to store numeric data, and decide whether to use int or float at decode
// Convert the data to string by decode
func main(a) {
var data = []byte({"status": 200})
var result map[string]interface{}
var decoder = json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
iferr := decoder.Decode(&result); err ! =nil {
log.Fatalln(err)
}
var status uint64
err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status);
checkError(err)
fmt.Println("Status value: ", status)
}
Copy the code
– Use the struct type to map the data you need to numeric
// Struct specifies the field type
func main(a) {
var data = []byte(`{"status": 200}`)
var result struct {
Status uint64 `json:"status"`
}
err := json.NewDecoder(bytes.NewReader(data)).Decode(&result)
checkError(err)
fmt.Printf("Result: %+v", result)
}
Copy the code
-
You can use struct to map numeric types to json.RawMessage native data types
If the JSON data is not worried by decode or the value type of a JSON field is not fixed, etc.
// The status name can be int or string and is specified as json.rawMessage
func main(a) {
records := [][]byte{[]byte(`{"status":200, "tag":"one"}`),
[]byte(`{"status":"ok", "tag":"two"}`),}for idx, record := range records {
var result struct {
StatusCode uint64
StatusName string
Status json.RawMessage `json:"status"`
Tag string `json:"tag"`
}
err := json.NewDecoder(bytes.NewReader(record)).Decode(&result)
checkError(err)
var name string
err = json.Unmarshal(result.Status, &name)
if err == nil {
result.StatusName = name
}
var code uint64
err = json.Unmarshal(result.Status, &code)
if err == nil {
result.StatusCode = code
}
fmt.Printf("[%v] result => %+v\n", idx, result)
}
}
Copy the code
Struct, array, slice, map
We can compare struct variables using the equality operator == if the members of both structs are of comparable type:
type data struct {
num int
fp float32
complex complex64
str string
char rune
yes bool
events <-chan string
handler interface{}
ref *byte
raw [10]byte
}
func main(a) {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2: ", v1 == v2) // true
}
Copy the code
If any of the members of two constructs are not comparable, a compilation error will occur. Note that array members are only comparable if the array elements are comparable.
type data struct {
num int
checks [10]func(a) bool// No comparisondoIt func(a) bool// No comparisonm map[string]string// No comparisonbytes []byte// Cannot compare}func main(a) {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2: ", v1 == v2)
}
Copy the code
invalid operation: v1 == v2 (struct containing [10]func() bool cannot be compared)
Go provides library functions to compare variables that can’t be compared using ==, such as DeepEqual() using the “reflect” package:
// Compare elements that cannot be compared by the equality operator
func main(a) {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2: ", reflect.DeepEqual(v1, v2)) // true
m1 := map[string]string{"one": "a"."two": "b"}
m2 := map[string]string{"two": "b"."one": "a"}
fmt.Println("v1 == v2: ", reflect.DeepEqual(m1, m2)) // true
s1 := []int{1.2.3}
s2 := []int{1.2.3}
// Note that the two slices are equal. The values and order must be the same
fmt.Println("v1 == v2: ", reflect.DeepEqual(s1, s2)) // true
}
Copy the code
This comparison may be slow, depending on your program’s needs. DeepEqual() has other uses:
func main(a) {
var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2: ", reflect.DeepEqual(b1, b2)) // false
}
Copy the code
Note:
DeepEqual()
Not always suitable for comparing slices
func main(a) {
var str = "one"
var in interface{} = "one"
fmt.Println("str == in: ", reflect.DeepEqual(str, in)) // true
v1 := []string{"one"."two"}
v2 := []string{"two"."one"}
fmt.Println("v1 == v2: ", reflect.DeepEqual(v1, v2)) // false
data := map[string]interface{} {"code": 200."value": []string{"one"."two"},
}
encoded, _ := json.Marshal(data)
var decoded map[string]interface{}
json.Unmarshal(encoded, &decoded)
fmt.Println("data == decoded: ", reflect.DeepEqual(data, decoded)) // false
}
Copy the code
To compare English text in byte or string case insensitive, use the ToUpper() and ToLower() functions of the “bytes” or “strings” package. To compare bytes or strings from other languages, use bytes.equalfold () and strings.equalfold ().
If byte slice contains data to authenticate the user (ciphertext hash, token, etc.), reflect.deepequal (), bytes.equal (), and bytes.Compare() should not be used. This three functions easy to program timing attacks, at this time you should use “crypto/subtle” is subtle in the package. ConstantTimeCompare () function
reflect.DeepEqual()
Empty slice is not equal to nil slice, but be carefulbyte.Equal()
They would be considered equal:
func main(a) {
var b1 []byte = nil
b2 := []byte{}
// B1 and B2 are of the same length and have the same byte order
// Nil is the same as slice in bytes
fmt.Println("b1 == b2: ", bytes.Equal(b1, b2)) // true
}
Copy the code
39. Recover from panic
Call recover() in a deferred function, and it catches/interrupts panic
// An example of an incorrect recover call
func main(a) {
recover(a)// Catch nothing
panic("not good") // A panic occurs and the main program exits
recover(a)// Will not be executed
println("ok")}// Example of correct recover call
func main(a) {
defer func(a) {
fmt.Println("recovered: ".recover() ()}panic("not good")}Copy the code
As you can see above, recover() only takes effect when called in the function that defer executes.
// An example of an incorrect call
func main(a) {
defer func(a) {
doRecover()
}()
panic("not good")}func doRecover(a) {
fmt.Println("recobered: ".recover()}Copy the code
recobered: panic: not good
40. Update elements by updating references as range iterates over Slice, array, and map
In a range iteration, the value is actually a copy of the element’s value. Updating the copy does not change the original element, that is, the address of the copied element is not the address of the original element:
func main(a) {
data := []int{1.2.3}
for _, v := range data {
v *= 10 // The original elements in the data are not modified
}
fmt.Println("data: ", data) // data: [1 2 3]
}
Copy the code
If you want to change the value of the original element, you should access it directly using the index:
func main(a) {
data := []int{1.2.3}
for i, v := range data {
data[i] = v * 10
}
fmt.Println("data: ", data) // data: [10 20 30]
}
Copy the code
If your collection holds Pointers to values, modify them a bit. You still need to use the index to access the elements, but you can use the range element to update the original value directly:
func main(a) {
data := []*struct{ num int} {{1}, {2}, {3}},for _, v := range data {
v.num *= 10 // Use pointer update directly
}
fmt.Println(data[0], data[1], data[2]) / / & {10} and {} 20 & {30}
}
Copy the code
41. Hidden data in slice
When a new slice is cut from a slice, the new slice references the underlying array of the original slice. If this hole is skipped, the program may allocate large amounts of temporary slice to point to parts of the original underlying array, resulting in unpredictable memory usage.
func get(a) []byte {
raw := make([]byte.10000)
fmt.Println(len(raw), cap(raw), &raw[0]) // 10000 10000 0xc420080000
return raw[:3] // Redistribute slice with a size of 10000
}
func main(a) {
data := get()
fmt.Println(len(data), cap(data), &data[0]) // 3 10000 0xc420080000
}
Copy the code
This can be solved by copying the data from a temporary slice, rather than re-slicing:
func get(a) (res []byte) {
raw := make([]byte.10000)
fmt.Println(len(raw), cap(raw), &raw[0]) // 10000 10000 0xc420080000
res = make([]byte.3)
copy(res, raw[:3])
return
}
func main(a) {
data := get()
fmt.Println(len(data), cap(data), &data[0]) // 3 3 0xc4200160b8
}
Copy the code
Misuse of data in Slice
As a simple example, overwrite the file path (stored in Slice)
Split the path to point to each directory at a different level, change the first directory name and reorganize the subdirectory names to create a new path:
// Example of incorrect use of slice stitching
func main(a) {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path, '/') / / 4
println(sepIndex)
dir1 := path[:sepIndex]
dir2 := path[sepIndex+1:]
println("dir1: ".string(dir1)) // AAAA
println("dir2: ".string(dir2)) // BBBBBBBBB
dir1 = append(dir1, "suffix"...).println("current path: ".string(path)) // AAAAsuffixBBBB
path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'})
println("dir1: ".string(dir1)) // AAAAsuffix
println("dir2: ".string(dir2)) // uffixBBBB
println("new path: ".string(path)) AAAAsuffix/uffixBBBB // Error result
}
Copy the code
AAAAsuffix/BBBBBBBBB = AAAAsuffix/BBBBBBB = AAAAsuffix/BBBBBBB = AAAAsuffix/BBBBBBB
Solution:
- Reallocate the new slice and copy the data you need
- Using a full slice expression:
input[low:high:max]
, the capacity is adjusted to max-low
// Use full slice expression
func main(a) {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path, '/') / / 4
dir1 := path[:sepIndex:sepIndex] // Cap (dir1) is set to 4 instead of 16
dir2 := path[sepIndex+1:]
dir1 = append(dir1, "suffix"...). path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'})
println("dir1: ".string(dir1)) // AAAAsuffix
println("dir2: ".string(dir2)) // BBBBBBBBB
println("new path: ".string(path)) // AAAAsuffix/BBBBBBBBB
}
Copy the code
The third argument in line 6 is used to control the new capacity of dir1, and a new buffer will be allocated to hold any excess elements in dir1. Rather than overwriting the original path underlying array
43. The old slice
When you create a new slice from an existing slice, the data for both slices points to the same underlying array. If your program uses this feature, be aware of stale slice issues.
In some cases, when an element is appended to a slice and the underlying array it points to is insufficient, a new array will be allocated to store the data. Other slices also point to the old underlying array.
// If the capacity is exceeded, the array will be reallocated to copy the values and store them again
func main(a) {
s1 := []int{1.2.3}
fmt.Println(len(s1), cap(s1), s1) // 3 3 [1 2 3]
s2 := s1[1:]
fmt.Println(len(s2), cap(s2), s2) // 2 2 [2 3]
for i := range s2 {
s2[i] += 20
}
// In this case, s1 and s2 refer to the same underlying array
fmt.Println(s1) / / 22 23 [1]
fmt.Println(s2) / / [23] 22
s2 = append(s2, 4) // Append an element to s2 of size 2, and a new array is allocated to store it
for i := range s2 {
s2[i] += 10
}
fmt.Println(s1) // [1 22 23] // At this point, s1 is no longer updated
fmt.Println(s2) / / 32 33 [14]
}
Copy the code
44. Type declarations and methods
When creating a new type from an existing non-interface type, it does not inherit the original method:
// Define a custom type for Mutex
type myMutex sync.Mutex
func main(a) {
var mtx myMutex
mtx.Lock()
mtx.UnLock()
}
Copy the code
mtx.Lock undefined (type myMutex has no field or method Lock)…
If you need to use the methods of the old type, you can embed the old type as an anonymous field in the new struct you define:
// Types are directly embedded in the form of fields
type myLocker struct {
sync.Mutex
}
func main(a) {
var locker myLocker
locker.Lock()
locker.Unlock()
}
Copy the code
The interface type declaration also preserves its set of methods:
type myLocker sync.Locker
func main(a) {
var locker myLocker
locker.Lock()
locker.Unlock()
}
Copy the code
45. Jump out of the for-switch and for-select blocks
If you do not specify a label for a break, the switch/ SELECT statement will be displayed. If you cannot use a return statement, you can use a block of code for the break label:
// Break and label jump out of the specified code block
func main(a) {
loop:
for {
switch {
case true:
fmt.Println("breaking out...")
// breaking out... // breaking out...
break loop
}
}
fmt.Println("out...")}Copy the code
Goto can also jump to a specified location, but it will still enter the for-switch again, in an infinite loop.
46. Iteration variables and closures in for statements
The iteration variables in the for statement are reused in each iteration, that is, the closure function created in the for always receives the same variable as an argument, and the same iteration value at the start of goroutine execution:
func main(a) {
data := []string{"one"."two"."three"}
for _, v := range data {
go func(a) {
fmt.Println(v)
}()
}
time.Sleep(3 * time.Second)
// Output three three three
}
Copy the code
The simplest solution: without modifying the goroutine function, use local variables inside for to store the iteration values and pass the arguments:
func main(a) {
data := []string{"one"."two"."three"}
for _, v := range data {
vCopy := v
go func(a) {
fmt.Println(vCopy)
}()
}
time.Sleep(3 * time.Second)
// Output one two three
}
Copy the code
Another solution: simply pass the current iteration value as a parameter to the anonymous function:
func main(a) {
data := []string{"one"."two"."three"}
for _, v := range data {
go func(in string) {
fmt.Println(in)
}(v)
}
time.Sleep(3 * time.Second)
// Output one two three
}
Copy the code
Note the following three slightly more complex example differences:
type field struct {
name string
}
func (p *field) print(a) {
fmt.Println(p.name)
}
// Error examples
func main(a) {
data := []field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
// Output three three three
}
// Correct example
func main(a) {
data := []field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
v := v
go v.print()
}
time.Sleep(3 * time.Second)
// Output one two three
}
// Correct example
func main(a) {
data := []*field{{"one"}, {"two"}, {"three"}}
for _, v := range data { // In this case, the iteration value v is the address of the three element values, each time v points to a different value
go v.print()
}
time.Sleep(3 * time.Second)
// Output one two three
}
Copy the code
47. The parameter values of the defer function
As of defer, its arguments are evaluated at declaration time, not execution time:
// In the defer function, the arguments are evaluated in advance
func main(a) {
var i = 1
defer fmt.Println("result: ".func(a) int { return i * 2 }())
i++
}
Copy the code
result: 2
48. The execution time of the defer function
The function that executes as defer will be executed at the end of the function that called it, not at the end of the block that called it, be careful to distinguish between.
For example, in a long-executing function, use defer in the internal for loop to clear the resource calls generated by each iteration.
// The command-line argument specifies the directory name
// Iterate over the files in the read directory
func main(a) {
if len(os.Args) ! =2 {
os.Exit(1)
}
dir := os.Args[1]
start, err := os.Stat(dir)
iferr ! =nil| |! start.IsDir() { os.Exit(2)}var targets []string
filepath.Walk(dir, func(fPath string, fInfo os.FileInfo, err error) error {
iferr ! =nil {
return err
}
if! fInfo.Mode().IsRegular() {return nil
}
targets = append(targets, fPath)
return nil
})
for _, target := range targets {
f, err := os.Open(target)
iferr ! =nil {
fmt.Println("bad target:", target, "error:", err) //error:too many open files
break
}
defer f.Close() // File resources are not closed at the end of each for block
// Use the f resource}}Copy the code
Create 10000 files first:
#! /bin/bashfor n in {1.. 10000}; do echo content > "file${n}.txt" doneCopy the code
Operation effect:
Solution: write the deferred function to an anonymous function:
// Directory traversal is normal
func main(a) {
// ...
for _, target := range targets {
func(a) {
f, err := os.Open(target)
iferr ! =nil {
fmt.Println("bad target:", target, "error:", err)
return // Use return instead of break in an anonymous function
}
defer f.Close() // When the anonymous function is finished, the call closes the file resource
// Use the f resource(1)}}}Copy the code
Of course, you can remove defer and just call f.close () after the file resource has been used.
49. Failed type assertion
In a type assertion statement, a “zero value” of the target type is returned if the assertion fails, and an exception may occur if the assertion variable is mixed with the original variable:
// Error examples
func main(a) {
var data interface{} = "great"
/ / data combination
if data, ok := data.(int); ok {
fmt.Println("[is an int], data: ", data)
} else {
fmt.Println("[not an int], data: ", data) // [isn't a int], data: 0}}// Correct example
func main(a) {
var data interface{} = "great"
if res, ok := data.(int); ok {
fmt.Println("[is an int], data: ", res)
} else {
fmt.Println("[not an int], data: ", data) // [not an int], data: great}}Copy the code
50. Blocked Gorutinue and resource leak
At Google I/O 2012, Rob Pike’s Go Concurrency Patterns talk discussed some of Go’s basic Concurrency Patterns, such as a function that picks up the first data in a data set in complete code:
func First(query string, replicas []Search) Result {
c := make(chan Result)
replicaSearch := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go replicaSearch(i)
}
return <-c
}
Copy the code
Each goroutine will send its search results to the results channel. The first data received in the channel will be returned directly.
What happens to the other goroutine results after the first data is returned? What about their own coroutines?
The resulting channel in First() is unbuffered, which means that only the First goroutine will return. Since there is no receiver, the other goroutines will block on the transmit. If you make a lot of calls, you may cause resource leaks.
To avoid leaks, you should make sure that all goroutines exit correctly. There are 2 solutions:
- Use a buffered channel to ensure that all goroutines are received:
func First(query string, replicas ... Search) Result {
c := make(chan Result,len(replicas))
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
Copy the code
-
Use a select statement with a channel default statement that holds a cached value:
The buffer channel of default guarantees that even if the result channel does not receive data, it will not block the Goroutine
func First(query string, replicas ... Search) Result {
c := make(chan Result,1)
searchReplica := func(i int) {
select {
case c <- replicas[i](query):
default:}}for i := range replicas {
go searchReplica(i)
}
return <-c
}
Copy the code
- Use the special Cancellation channel to interrupt the execution of the remaining Goroutine:
func First(query string, replicas ... Search) Result {
c := make(chan Result)
done := make(chan struct{})
defer close(done)
searchReplica := func(i int) {
select {
case c <- replicas[i](query):
case <- done:
}
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
Copy the code
Rob Pike did not mention these problems in the presentation code in order to simplify the presentation. However, for beginners, it may be used without thinking.
Advanced: 51-57
51. Use a pointer as a method receiver
As long as the value is addressable, pointer methods can be called directly on the value. For a method, it is sufficient that its receiver is a pointer.
But not all values are addressable, such as elements of type Map, variables referenced through an interface:
type data struct {
name string
}
type printer interface {
print()}func (p *data) print(a) {
fmt.Println("name: ", p.name)
}
func main(a) {
d1 := data{"one"}
d1.print(a)// The d1 variable is addressable and calls the pointer receiver's methods directly
var in printer = data{"two"}
in.print(a)// Type mismatch
m := map[string]data{
"x": data{"three"},
}
m["x"].print(a)// m["x"] is not addressable // changes frequently
}
Copy the code
cannot use data literal (type data) as type printer in assignment:
data does not implement printer (print method has pointer receiver)
cannot call pointer method on m[“x”] cannot take the address of m[“x”]
52. Update the value of the Map field
If the value of a map field is of the struct type, it is not possible to update a single field of that struct directly:
// Cannot directly update the struct field value
type data struct {
name string
}
func main(a) {
m := map[string]data{
"x": {"Tom"},
}
m["x"].name = "Jerry"
}
Copy the code
cannot assign to struct field m[“x”].name in map
Because the elements in the map are not addressable. What is distinguishable is that elements of slice are addressable:
type data struct {
name string
}
func main(a) {
s := []data{{"Tom"}}
s[0].name = "Jerry"
fmt.Println(s) // [{Jerry}]
}
Copy the code
Note: A while ago the GCCGO compiler was able to update the field values of map struct elements, but this was soon fixed. It is officially considered a potential feature of Go1.3 and is still in the Todo list without timely implementation.
Update the field value of a struct element in a map.
- Using local variables
// Extract the entire struct into a local variable, modify the field value and then assign the entire struct
type data struct {
name string
}
func main(a) {
m := map[string]data{
"x": {"Tom"},
}
r := m["x"]
r.name = "Jerry"
m["x"] = r
fmt.Println(m) // map[x:{Jerry}]
}
Copy the code
- Use a map pointer to an element
func main(a) {
m := map[string]*data{
"x": {"Tom"},
}
m["x"].name = "Jerry" // Modify the fields in m["x"] directly
fmt.Println(m["x"]) // &{Jerry}
}
Copy the code
But watch out for the following misuse:
func main(a) {
m := map[string]*data{
"x": {"Tom"},
}
m["z"].name = "what???"
fmt.Println(m["x"])}Copy the code
panic: runtime error: invalid memory address or nil pointer dereference
53. Nil interface and nil interface values
Although interface looks like a pointer type, it is not. A variable of type interface is nil only if both type and value are nil
If the value of your interface variable changes with other variables (fog), be careful when comparing it to nil:
func main(a) {
var data *byte
var in interface{}
fmt.Println(data, data == nil) // <nil> true
fmt.Println(in, in == nil) // <nil> true
in = data
fmt.Println(in, in == nil) //
false // The data value is nil but the in value is not nil
}
Copy the code
If your function returns a value of type interface, beware of this pit:
// Error examples
func main(a) {
doIt := func(arg int) interface{} {
var result *struct{} = nil
if arg > 0 {
result = &struct{}{}
}
return result
}
if res := doIt(- 1); res ! =nil {
fmt.Println("Good result: ", res) // Good result:
fmt.Printf("%T\n", res) // *struct {} // res is not nil
fmt.Printf("%v\n", res) // <nil>}}// Correct example
func main(a) {
doIt := func(arg int) interface{} {
var result *struct{} = nil
if arg > 0 {
result = &struct{}{}
} else {
return nil // Return nil explicitly
}
return result
}
if res := doIt(- 1); res ! =nil {
fmt.Println("Good result: ", res)
} else {
fmt.Println("Bad result: ", res) // Bad result:
}}Copy the code
54. Stack variables
You don’t always know whether your variables are allocated to the heap or the stack.
In C++, variables created with new are always allocated to the heap, but in Go, even if new() and make() are used to create variables, the location of the variables is still managed by the Go compiler.
The Go compiler determines where to store a variable based on its size and the result of its “escape analysis”, so it can return exactly the address of the local variable, which is not possible in C/C++.
In go build or go run, add the -m parameter to accurately analyze the variable allocation position of the program:
55. Concurrency and Parallelism
With Go 1.4 and below, only one execution context/OS thread will be used, meaning that at most one Goroutine will be executing at any one time.
Go 1.5 sets the number of executable contexts to the number of logical CPU cores returned by Runtime.numCPU (). Whether this is consistent with the actual total number of logical CPU cores in the system depends on the number of CPU cores assigned to your program. This can be adjusted using the GOMAXPROCS environment variable or dynamically using runtime.gomaxprocs ().
Myth: GOMAXPROCS is the number of CPU cores that execute a Goroutine, see the documentation
The value of GOMAXPROCS can exceed the actual number of cpus, with a maximum of 256 in 1.5
func main(a) {
fmt.Println(runtime.GOMAXPROCS(- 1)) / / 4
fmt.Println(runtime.NumCPU()) / / 4
runtime.GOMAXPROCS(20)
fmt.Println(runtime.GOMAXPROCS(- 1)) / / 20
runtime.GOMAXPROCS(300)
fmt.Println(runtime.GOMAXPROCS(- 1)) // Go 1.9.2 // 300
}
Copy the code
56. Reordering of read and write operations
Go may reorder the execution of some operations, which can guarantee that operations are executed sequentially in a single goroutine, but does not guarantee the execution order of multiple goroutines:
var _ = runtime.GOMAXPROCS(3)
var a, b int
func u1(a) {
a = 1
b = 2
}
func u2(a) {
a = 3
b = 4
}
func p(a) {
println(a)
println(b)
}
func main(a) {
go u1() // Multiple goroutines are executed in varying order
go u2()
go p()
time.Sleep(1 * time.Second)
}
Copy the code
Operation effect:
If you want to keep multiple Goroutines executing in the same order as in your code, you can use the locking mechanism in a channel or sync package.
57. Prioritize scheduling
Your program might have one Goroutine that prevents other Goroutines from running at runtime, such as a for loop that doesn’t let the scheduler run:
func main(a) {
done := false
go func(a) {
done = true} ()for! done { }println("done !")}Copy the code
The body of the for loop does not have to be empty, but there is a problem if the code does not trigger scheduler execution.
The scheduler executes after GC, Go declarations, blocking channels, blocking system calls, and locking operations. It also executes on non-inline function calls:
func main(a) {
done := false
go func(a) {
done = true} ()for! done {println("not done !") // Does not execute inline
}
println("done !")}Copy the code
We can parse inline functions called in the for block by adding the -m argument:
You can also manually start the scheduler using Gosched() in the Runtime package:
func main(a) {
done := false
go func(a) {
done = true} ()for! done { runtime.Gosched() }println("done !")}Copy the code
Operation effect:
Reprinted fromgithub.com/wuYin/blog
This article is also published in the wechat public number [Trail Information], welcome to scan the code to follow!