Go is a simple and fun language, but like other languages, it comes with a few tricks… Most of these techniques are not the result of Go’s flaws. Some of these mistakes are natural pitfalls if you’ve spoken another language before. Others are caused by faulty assumptions and a lack of detail.
Most of these tips are obvious if you take the time to learn the language, read the official notes, wikis, mailing list discussions, numerous excellent blog posts and Rob Pike’s presentation, and the source code. It doesn’t matter that not everyone starts out this way. If you’re new to Go, this information will save you a lot of time debugging your code.
directory
-
Primary article
-
Opening braces cannot be placed on a separate line
-
Unused variables
-
Unused Imports
-
A simple variable declaration can only be used inside a function
-
Declare variables repeatedly using simple declarations
-
Accidental Variable Shadowing
-
You cannot initialize a variable with “nil” without using an explicit type
-
Use “nil” Slices and Maps
-
The capacity of the Map
-
String will not be nil
-
Parameter of the Array function
-
Undesired values that occur when the “range” statement is used in Slice and Array
-
Slices and Arrays are one-dimensional
-
Access Map Keys that do not exist
-
Strings cannot be modified
-
Conversion between String and Byte Slice
-
String and index operations
-
Strings are not always UTF8 text
-
Length of string
-
Missing commas in multi-line Slice, Array, and Map statements
-
Log. Fatal and log.Panic are not just logs
-
The built-in data structure operations are not synchronous
-
The iterated value of String in the “range” statement
-
Use the “for range” statement to iterate over the Map
-
Failure behavior in the “switch” declaration
-
Autoincrement and autodecrement
-
Operate by bit NOT
-
Differences in operation priorities
-
Unexported structures are not encoded
-
There are active Goroutines under the app exit
-
A message is sent to an uncached Channel and returned as soon as the target receiver is ready
-
Sending to a closed Channel causes Panic
-
The use of “nil” Channels
-
The recipient of the pass method cannot modify the original value
-
Advanced article
-
Turn off the HTTP response
-
Close the HTTP connection
-
Compare Structs, Arrays, Slices, and Maps
-
Recover from Panic
-
Update the value of the referenced element in the Slice, Array, and Map “range” statement
-
“Hide” data in Slice
-
Slice data “corrupted”
-
“Stale “Slices
-
Type declarations and methods
-
Jump out of the “for switch” and “for Select “blocks
-
Iterating variables and closures in the “for” declaration
-
Defer evaluates the function call parameters
-
The function call from Defer executes
-
Failed type assertion
-
Blocked Goroutine and resource leaks
-
Senior post
-
An instance that receives the value of a method using a pointer
-
Update the Map value
-
The value of “nil” Interfaces and “nil” Interfaces
-
Stack and heap variables
-
GOMAXPROCS, concurrency, and parallelism
-
Reordering of read and write operations
-
Priority scheduling
Primary article
Opening braces cannot be placed on a separate line
- level: beginner
In most other languages that use braces, you need to choose where to place them. Go is different. You can thank automatic semicolon injection for this (no prereading). Yes, there’s a semicolon in Go: -)
Examples of failures:
package main import"fmt" func main(){//error, can't have the opening brace on a separate line fmt.Println("hello there!" )}Copy the code
Compilation error:
/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {
Copy the code
Examples in action:
package main import"fmt" func main(){ fmt.Println("works!" )}Copy the code
Unused variables
- level: beginner
If you have unused variables, the code will fail to compile. There are exceptions, of course. By all means use declared variables inside functions, but unused global variables are fine.
If you assign a new value to an unused variable, the code will still fail to compile. You need to use this variable somewhere in order for the compiler to compile happily.
Fails:
package main
var gvar int//not an error
func main(){var one int//error, unused variable
two :=2//error, unused variablevar three int//error, even though it's assigned 3 on the next line
three =3}
Copy the code
Compile Errors:
/tmp/sandbox473116179/main.go:6: one declared andnot used /tmp/sandbox473116179/main.go:7: two declared andnot used /tmp/sandbox473116179/main.go:8: three declared andnot used
Copy the code
Works:
package main
import"fmt"
func main(){var one int
_ = one
two :=2
fmt.Println(two)var three int
three =3
one = three
var four int
four = four
}
Copy the code
Another option is to comment out or remove unused variables: -)
Unused Imports
- level: beginner
If you import a package without using any of its functions, interfaces, constructs, or variables, the code will fail to compile.
If you really need the imported package, you can add an underscore marker, _, as the package name to avoid compile failures. The slide line marker is used for introduction, but is not used.
Fails:
package main
import("fmt""log""time")
func main(){}
Copy the code
Compile Errors:
/tmp/sandbox627475386/main.go:4: imported andnot used:"fmt"/tmp/sandbox627475386/main.go:5: imported andnot used:"log"/tmp/sandbox627475386/main.go:6: imported andnot used:"time"
Copy the code
Works:
package main
import(
_ "fmt""log""time")var _ = log.Println
func main(){
_ = time.Now}
Copy the code
Another option is to remove or comment out unused imports: -)
A simple variable declaration can only be used inside a function
- level: beginner
Fails:
package main
myvar :=1//error
func main(){}
Copy the code
Compile Error:
/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
Copy the code
Works:
package main
var myvar =1
func main(){}
Copy the code
Declare variables repeatedly using simple declarations
- level: beginner
You can’t declare a variable twice in a single declaration, but this is allowed in multivariable declarations, where at least one new variable must be declared.
Duplicate variables need to be in the same code block, otherwise you will get a hidden variable.
Fails:
package main
func main(){
one :=0
one :=1//error}
Copy the code
Compile Error:
/tmp/sandbox706333626/main.go:5:nonew variables on left side of :=
Copy the code
Works:
Package main func main(){one :=0 one,two :=1,2 one,two = two,one}Copy the code
Accidental Variable Shadowing
- level: beginner
The syntax of short variable declarations is so convenient (especially for developers who have worked with dynamic languages) that it’s easy to think of it as a normal allocation operation. If you make this mistake in a new block of code, there will be no compilation error, but your application will not do what you expect.
package main
import"fmt"
func main(){
x :=1
fmt.Println(x)//prints 1{
fmt.Println(x)//prints 1
x :=2
fmt.Println(x)//prints 2}
fmt.Println(x)//prints 1 (bad if you need 2)}
Copy the code
This is a very common pitfall even for experienced Go developers. The hole is easy to dig but hard to find.
You cannot initialize a variable with “nil” without using an explicit type
- level: beginner
The “nil” flag is used to represent “zero values” for interfaces, functions, maps, Slices, and channels. If you do not specify the type of the variable, the compiler will not be able to compile your code because it cannot guess the specific type.
Fails:
package main
func main(){var x =nil//error
_ = x
}
Copy the code
Compile Error:
/tmp/sandbox188239583/main.go:4:use of untyped nil
Copy the code
Works:
package main
func main(){var x interface{}=nil
_ = x
}
Copy the code
Use “nil” Slices and Maps
- level: beginner
Adding elements to a “nil” slice is fine, but doing the same for a map will generate a runtime panic.
Works:
package main
func main(){var s []int
s = append(s,1)}
Copy the code
Fails:
package main
func main(){var m map[string]int
m["one"]=1//error}
Copy the code
The capacity of the Map
- level: beginner
You can specify the size of a map when it is created, but you cannot use the cap() function on the map.
Fails:
package main
func main(){
m := make(map[string]int,99)
cap(m)//error}
Copy the code
Compile Error:
/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int)for cap
Copy the code
String will not be nil
- level: beginner
This is a concern for developers who often use “nil” to assign string variables.
Fails:
package main
func main(){var x string=nil//errorif x ==nil{//error
x ="default"}}
Copy the code
Compile Errors:
/tmp/sandbox630560459/main.go:4: cannot usenilas type stringin assignment /tmp/sandbox630560459/main.go:6: invalid operation: x ==nil(mismatched types stringandnil)
Copy the code
Works:
package main
func main(){var x string//defaults to "" (zero value)if x ==""{
x ="default"}}
Copy the code
Parameter of the Array function
-level: beginner
If you’re a C or C++ developer, arrays are Pointers to you. When you pass an array to a function, the function refers to the same memory area so that they can modify the original data. Arrays in Go are numbers, so when you pass an array to a function, the function gets a copy of the original array data. This can be a problem if you want to update the array’s data.
Package main import" FMT "func main(){x :=[3]int{1,2,3} func(arr [3]int){arr[0]=7 FMT.Println(arr)//prints [7 2 3]}(x) fmt.Println(x)//prints [1 2 3] (not ok if you need [7 2 3])}Copy the code
If you need to update the data of the original array, you can use the array pointer type.
Package main import" FMT "func main(){x :=[3]int{1,2,3} func(arr *[3]int){(*arr)[0]=7 FMT.Println(arr)//prints &[7 2 3]}(&x) fmt.Println(x)//prints [7 2 3]}Copy the code
Another option is to use Slice. Even if your function gets a copy of the Slice variable, it still references the original data.
Package main import" FMT "func main(){x :=[]int{1,2,3} func(arr []int){arr[0]=7 FMT.Println(arr)//prints [7 2 3]}(x) fmt.Println(x)//prints [7 2 3]}Copy the code
Undesired values that occur when the “range” statement is used in Slice and Array
- level: beginner
This can happen if you use “for-in” or “foreach” statements in other languages. The “range” syntax in Go is a little different. It gets two values: the first value is the index of the element, and the other value is the element’s data.
Bad:
package main
import"fmt"
func main(){
x :=[]string{"a","b","c"}for v := range x {
fmt.Println(v)//prints 0, 1, 2}}
Copy the code
Good:
package main
import"fmt"
func main(){
x :=[]string{"a","b","c"}for _, v := range x {
fmt.Println(v)//prints a, b, c}}
Copy the code
Slices and Arrays are one-dimensional
- level: beginner
It may seem like Go supports multi-dimensional arrays and slices, but it doesn’t. Although you can create arrays of arrays or slices of slices. For numerical computing applications that rely on dynamic multidimensional arrays, Go is far from the performance and complexity.
You can build dynamic multidimensional arrays using pure one-dimensional arrays, slices of “independent” slices, and slices of “shared data” slices.
If you use a purely one-dimensional array, you need to deal with indexing, boundary checking, and memory reallocation when the array needs to be larger.
Creating a dynamic multi-dimensional array using a “standalone” slice requires two steps. First, you need to create an external slice. Then, you need to allocate each internal slice. The internal slices are independent of each other. You can add and subtract them without affecting the rest of the internal slice.
package main
func main(){
x :=2
y :=4
table := make([][]int,x)for i:= range table {
table[i]= make([]int,y)}}
Copy the code
Creating a dynamic multidimensional array using a slice of a “shared data” slice requires three steps. First, you need to create a data “container” to hold the raw data. Then, you create an external slice. Finally, each internal slice is initialized by re-slicing the original data slice.
package main import"fmt" func main(){ h, W :=2,4 raw := make([]int,h*w)for I := range raw {raw[I]= I} FMT.Println(raw,&raw[4])//prints: =2,4 raw := make([]int,h*w)for I := range raw {raw[I]= I} FMT.Println(raw,&raw[4]) [0 1 2 3 4 5 6 7] <ptr_addr_x> table := make([][]int,h)for i:= range table { table[i]= raw[i*w:i*w + w]} fmt.Println(table,&table[1][0])//prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>}Copy the code
There are already requests for multi-dimensional array and Slice, but this seems to be a low priority feature right now.
Access Map Keys that do not exist
-level: beginner
This is a trick (as it is done in other languages) for developers who want a “nil” identifier. If the “zero” value of the corresponding data type is “nil”, the value returned will be “nil”, but it is not the same for other data types. Detecting the corresponding “zero value” can be used to determine whether a record exists in a map, but this is not always trusted (for example, what to do if “zero” is false in a binary map). The most reliable way to detect the existence of records in a given map is to check the second value returned through the map’s access operation.
Bad:
package main
import"fmt"
func main(){
x := map[string]string{"one":"a","two":"","three":"c"}if v := x["two"]; v ==""{//incorrect
fmt.Println("no entry")}}
Copy the code
Good:
package main import"fmt" func main(){ x := map[string]string{"one":"a","two":"","three":"c"}if _,ok := x["two"]; ! ok { fmt.Println("no entry")}}Copy the code
Strings cannot be modified
- level: beginner
Attempting to update a single character in a string variable using an index operation will fail. String is a read-only Byte slice (with some additional attributes). If you do need to update a string, use Byte Slice and convert it to string if necessary.
Fails:
package main
import"fmt"
func main(){
x :="text"
x[0]='T'
fmt.Println(x)}
Copy the code
Compile Error:
/tmp/sandbox305565531/main.go:7: cannot assign to x[0]
Copy the code
Works:
package main
import"fmt"
func main(){
x :="text"
xbytes :=[]byte(x)
xbytes[0]='T'
fmt.Println(string(xbytes))//prints Text}
Copy the code
Note that this is not the correct way to update characters in a literal string, since a given character may be stored in multiple bytes. If you do need to update a literal string, convert it to a Rune slice first. Even with rune slice, it is possible for a single character to occupy multiple runes, such as when your character has a particular accent. This complex and ambiguous “character” nature is why Go strings are represented in byte sequences.
Conversion between String and Byte Slice
- level: beginner
When you convert a string to a Byte slice (or vice versa), you get a full copy of the original data. This is different from the cast operation in other languages, and from the reslice operation when a new slice variable points to the same array used by the original Byte slice.
Go does use some optimizations in the []byte to String and string to []byte conversions to avoid additional allocations (there are more optimizations in the TOdo list).
The first optimization avoids extra allocation when []bytekey is used to query in the map[string] collection: m[string(key)].
The second optimization avoids extra allocation in for range statements after converting strings to [] bytes: for I,v := range []byte(STR){… }.
String and index operations
- level: beginner
An index operation on a string returns a byte value, not a character (as in other languages).
package main
import"fmt"
func main(){
x :="text"
fmt.Println(x[0])//print 116
fmt.Printf("%T",x[0])//prints uint8}
Copy the code
If you need to access specific string “characters” (Unicode encoded points/runes), use for range. Official “unicode/utf8” pack and experimental utf8string package (golang.org/x/exp/utf8string) can also be used. The UTf8String package contains a handy At() method. Converting a string to a slice of rune is also an option.
Strings are not always UTF8 text
- level: beginner
The string value does not need to be UTF8 text. They can contain any byte. The string will only be UTF8 if string literal is used. Even if they can then use escape sequences to contain other data.
To know if a string is UTF8, you can use the ValidString() function in the “Unicode/UTF8” package.
package main
import("fmt""unicode/utf8")
func main(){
data1 :="ABC"
fmt.Println(utf8.ValidString(data1))//prints: true
data2 :="A\xfeC"
fmt.Println(utf8.ValidString(data2))//prints: false}
Copy the code
Length of string
- level: beginner
Let’s say you’re a Python developer and you have this code:
Data = u 'has' print (len (data)) # prints: 1Copy the code
You might be surprised when you convert it to Go code.
Package main import" FMT "func main(){data :="♥" FMT.Println(len(data))//prints: 3}Copy the code
The built-in len() function returns the number of bytes, not the number of characters in a Unicode string as calculated in Python.
To get the same result in Go, use the RuneCountInString() function in the “Unicode/UTF8” package.
Package main import(" FMT ""unicode/utf8") func main(){data :="♥" FMT.Println(utf8.runecountinString (data))//prints: 1}Copy the code
The RuneCountInString() function theoretically does not return the number of characters, since a single character can take up multiple runes.
Package main import(" FMT ""unicode/utf8") func main(){data :=" e "FMT.Println(data))//prints: 3 fmt.Println(utf8.RuneCountInString(data))//prints: 2}Copy the code
Missing commas in multi-line Slice, Array, and Map statements
- level: beginner
Fails:
Package main func main(){x :=[]int{1,2//error} _ = x}Copy the code
Compile Errors:
/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected }
Copy the code
Works:
Package main func main(){x :=[]int{1,2,} x = x y :=[]int{3,4,}//no error y = y}Copy the code
If you do not add the trailing comma when folding the declaration to a single line, you will not get a compilation error.
Log. Fatal and log.Panic are not just logs
- level: beginner
Logging libraries generally provide different log levels. Unlike these logging libraries, the log package in Go can do more than log when you call its Fatal*() and Panic*() functions. When your application calls these functions, Go will also terminate the application 🙂
package main
import"log"
func main(){
log.Fatalln("Fatal Level: log entry")//app exits here
log.Println("Normal Level: log entry")}
Copy the code
The built-in data structure operations are not synchronous
- level: beginner
Even though Go itself has many features to support concurrency, concurrency-safe data sets are not one of them 🙂 It is your responsibility to ensure that data sets are updated atomically. Goroutines and Channels are recommended ways to implement these atomic operations, but you can also use the “sync” package if it makes sense for your application.
The iterated value of String in the “range” statement
- level: beginner
The index value (the first value returned by the “range” operation) is the index of the first byte of the current “character” (Unicode-encoded point/rune) of the second value returned. It is not an index of the current “character”, unlike in other languages. Note that real characters may be represented by multiple runes. If you need to deal with characters, make sure you use the “norm” package (golang.org/x/text/unicode/norm).
The for range statement of the string variable will attempt to translate the data into UTF8 text. For any byte sequence it doesn’t understand, it returns 0xFFFD Runes (that is, Unicode replacement characters) instead of real data. If you have any (non-UTF8 text) data stored in string variables, be sure to convert them to Byte slice to get all the saved data.
package main
import"fmt"
func main(){
data :="A\xfe\x02\xff\x04"for _,v := range data {
fmt.Printf("%#x ",v)}//prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)
fmt.Println()for _,v := range []byte(data){
fmt.Printf("%#x ",v)}//prints: 0x41 0xfe 0x2 0xff 0x4 (good)}
Copy the code
Use the “for range” statement to iterate over the Map
- level: beginner
You need this technique if you want to get elements in a certain order (for example, by key). Each map iteration will produce a different result. The Go Runtime deliberately tries to randomize the iteration order, but it doesn’t always succeed, so you might end up with some of the same map iterations. So don’t be surprised if you see five iterations of the same result in a row.
package main
import"fmt"
func main(){
m := map[string]int{"one":1,"two":2,"three":3,"four":4}for k,v := range m {
fmt.Println(k,v)}}
Copy the code
And if you use Go’s playground (play.golang.org/), you’ll always get the same result, because it doesn’t recompile the code unless you change it.
Failure behavior in the “switch” declaration
- level: beginner
The “case” block in the “switch” declaration statement breaks by default. This is different from the default behavior in other languages of going to the next “next” block of code.
package main import"fmt" func main(){ isSpace := func(ch byte)bool{switch(ch){case' '://errorcase'\t':returntrue}returnfalse} fmt.Println(isSpace('\t'))//prints true (ok) fmt.Println(isSpace(' '))//prints false (not ok)}Copy the code
You can force a “case” block to enter by using “fallthrough” at the end of each “case” block. You can also override the switch statement to use the list of expressions in the “case” block.
package main
import"fmt"
func main(){
isSpace := func(ch byte)bool{switch(ch){case' ','\t':returntrue}returnfalse}
fmt.Println(isSpace('\t'))//prints true (ok)
fmt.Println(isSpace(' '))//prints true (ok)}
Copy the code
Autoincrement and autodecrement
- level: beginner
Many languages have increment and decrement operations. Unlike other languages, Go does not support pre-release operations. You also cannot use these operators in expressions.
Fails:
Package main import" FMT "func main(){data :=[]int{1,2,3} I :=0++ I //error fmt.Println(data[I ++])//error}Copy the code
Compile Errors:
/tmp/sandbox101231828/main.go:8: syntax error: unexpected ++/tmp/sandbox101231828/main.go:9: syntax error: unexpected ++, expecting :
Copy the code
Works:
Package main import" FMT "func main(){data :=[]int{1,2,3} I :=0 I ++ FMT.Println(data[I])}Copy the code
Operate by bit NOT
- level: beginner
Many languages use ~ as the unary NOT operator (that is, bitwise complement), but Go reuses the XOR operator (^) for this purpose.
Fails:
package main
import"fmt"
func main(){
fmt.Println(~2)//error}
Copy the code
Compile Error:
/tmp/sandbox965529189/main.go:6: the bitwise complement operatoris^
Copy the code
Works:
package main
import"fmt"
func main(){var d uint8 =2
fmt.Printf("%08b\n",^d)}
Copy the code
Go still uses ^ as the XOR operator, which may be confusing to some.
If you wish, you can use a binary XOR operation (e.g., 0x02 XOR 0xFF) to represent a unary NOT operation (e.g., NOT 0x02). This explains why ^ is reused to represent unary NOT operations.
Go also has a special ‘AND NOT’ bitwise operation (&^), which makes the NOT operation even more confusing. This seems to require A special feature /hack to support A AND (NOT B) without parentheses.
package main
import"fmt"
func main(){var a uint8 =0x82var 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
Differences in operation priorities
- level: beginner
In addition to the “bit Clear” operation (&^), Go is also a collection of standard operators shared with many other languages. Although operation priorities are not always the same.
package main
import"fmt"
func main(){
fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n",0x2&0x2+0x4)//prints: 0x2 & 0x2 + 0x4 -> 0x6//Go: (0x2 & 0x2) + 0x4//C++: 0x2 & (0x2 + 0x4) -> 0x2
fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n",0x2+0x2<<0x1)//prints: 0x2 + 0x2 << 0x1 -> 0x6//Go: 0x2 + (0x2 << 0x1)//C++: (0x2 + 0x2) << 0x1 -> 0x8
fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf|0x2^0x2)//prints: 0xf | 0x2 ^ 0x2 -> 0xd//Go: (0xf | 0x2) ^ 0x2//C++: 0xf | (0x2 ^ 0x2) -> 0xf}
Copy the code
Unexported structures are not encoded
- level: beginner
Structures that begin with a lowercase letter will not be encoded (JSON, XML, GOB, etc.), so when you encode these unexported structures, you will get zero.
Fails:
package main
import("fmt""encoding/json")
type MyDatastruct{Oneint
two string}
func main(){in:=MyData{1,"two"}
fmt.Printf("%#v\n",in)//prints main.MyData{One:1, two:"two"}
encoded,_ := json.Marshal(in)
fmt.Println(string(encoded))//prints {"One":1}varoutMyData
json.Unmarshal(encoded,&out)
fmt.Printf("%#v\n",out)//prints main.MyData{One:1, two:""}}
Copy the code
There are active Goroutines under the app exit
- level: beginner
Applications will not complete with all goroutines. This is a very common mistake for beginners. Everyone starts at a certain point, so there’s no shame in making beginner’s mistakes 🙂
package main import("fmt""time") func main(){ workerCount :=2for 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) fmt.Printf("[%v] is done\n",workerId)}Copy the code
You will see:
[0]is running
[1]is running
all done!
Copy the code
One of the most common solutions is to use the “WaitGroup” variable. It will make the main Goroutine wait for all worker Goroutines to complete. If your app has workers with long-running message processing loops, you will also need a method to signal these Goroutines to exit. You can send a “kill” message to each worker. Another option is to close a channel that all workers receive. This is a simple way to send a signal to all goroutines at once.
package main import("fmt""sync") func main(){var wg sync.WaitGroupdone:= make(chan struct{}) workerCount :=2for 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
If you run the app, you will see:
[0]is running
[0]isdone[1]is running
[1]isdone
Copy the code
It looks like all workers are finished before the main goroutine exits. Great! However, you will also see this:
fatal error: all goroutines are asleep - deadlock!
Copy the code
This is not good 🙂 what was sent? Why do deadlocks occur? The worker exits and they also execute Wg.done (). The app should be fine.
Deadlocks occur because each worker gets a copy of the original “WaitGroup” variable. When the worker executes Wg.done (), it does not take effect on the “WaitGroup” variable on the main goroutine.
package main import("fmt""sync") func main(){var wg sync.WaitGroupdone:= make(chan struct{}) wq := make(chan interface{}) workerCount :=2for i :=0; i < workerCount; i++{ wg.Add(1) go doit(i,wq,done,&wg)}for i :=0; i < workerCount; i++{ wq <- i } close(done) wg.Wait() fmt.Println("all done!" )} func doit(workerId int, wq <-chan interface{},done<-chan struct{},wg *sync.WaitGroup){ fmt.Printf("[%v] is running\n",workerId) defer wg.Done()for{select{case m :=<- wq: fmt.Printf("[%v] m => %v\n",workerId,m)case<-done: fmt.Printf("[%v] is done\n",workerId)return}}}Copy the code
Now it will work as expected 🙂
A message is sent to an uncached Channel and returned as soon as the target receiver is ready
- level: beginner
The sender will not be blocked unless the message is being processed by the receiver. Depending on the machine on which you are running the code, the receiver’s Goroutine may or may not have enough time to process the message before the sender continues.
package main
import"fmt"
func main(){
ch := make(chan string)
go func(){for m := range ch {
fmt.Println("processed:",m)}}()
ch <-"cmd.1"
ch <-"cmd.2"//won't be processed}
Copy the code
Sending to a closed Channel causes Panic
- level: beginner
It is safe to receive from a closed channel. The return value of OK in the received state will be set to false, meaning that no data will be received. If you receive from a cached channel, you will get the cached data first, and once it is empty, the return OK value will be false.
Sending data to a closed channel causes panic. This behavior is well documented, but the intuition is different for new Go developers, who may want the sending behavior to resemble the receiving behavior.
package main
import("fmt""time")
func main(){
ch := make(chan int)for i :=0; i <3; i++{
go func(idx int){
ch <-(idx +1)*2}(i)}//get the first result
fmt.Println(<-ch)
close(ch)//not ok (you still have other senders)//do other work
time.Sleep(2* time.Second)}
Copy the code
Depending on the application, the repair method will be different. It could be a minor code change, or it could be an application design change. Either way, you need to make sure that your application does not send data to closed channels.
The buggy example above can be fixed by using a special deprecated channel to signal to the remaining workers that their results are no longer needed.
package main
import("fmt""time")
func main(){
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,"sent result")case<-done: fmt.Println(idx,"exiting")}}(i)}//get first result
fmt.Println("result:",<-ch)
close(done)//do other work
time.Sleep(3* time.Second)}
Copy the code
The use of “nil” Channels
- level: beginner
Send and receive operations on a nil channel are permanently blocked. This behavior is well documented, but it comes as a surprise to new Go developers.
package main
import("fmt""time")
func main(){var ch chan intfor i :=0; i <3; i++{
go func(idx int){
ch <-(idx +1)*2}(i)}//get first result
fmt.Println("result:",<-ch)//do other work
time.Sleep(2* time.Second)}
Copy the code
If you run the code you will see a Runtime error:
fatal error: all goroutines are asleep - deadlock!
Copy the code
This behavior can be used in the SELECT declaration to dynamically open and close methods of case code blocks.
package main
import"fmt"import"time"
func main(){
inch := make(chan int)
outch := make(chan int)
go func(){varin<- chan int= inch
varout chan <-intvar val intfor{select{caseout<- val:out=nilin= inch
case val =<-in:out= outch
in=nil}}}()
go func(){for r := range outch {
fmt.Println("result:",r)}}()
time.Sleep(0)
inch <-1
inch <-2
time.Sleep(3* time.Second)}
Copy the code
The recipient of the pass method cannot modify the original value
- level: beginner
The receiver of a method is just like a regular function parameter. If declared as a value, then your function/method gets a copy of the receiver argument. This means that changes made to the receiver will not affect the original value unless the receiver is a map or slice variable and you update an element in the collection, or the receiver of the field you update is a pointer.
package main
import"fmt"
type data struct{
num int
key *string
items map[string]bool}
func (this*data) pmethod(){this.num =7}
func (this data) vmethod(){this.num =8*this.key ="v.key"this.items["vmethod"]=true}
func main(){
key :="key.1"
d := data{1,&key,make(map[string]bool)}
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)//prints num=1 key=key.1 items=map[]
d.pmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)//prints num=7 key=key.1 items=map[]
d.vmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)//prints num=7 key=v.key items=map[vmethod:true]}
Copy the code
Levy. At /blog/11