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