Go's 50 holes: pitfalls, tips and common mistakes for new Golang developers [1]

Keywords: JSON Python xml encoding

Go is a simple and interesting language, but like other languages, it has some skills... Most of these techniques are not due to go's flaws. If you used to use other languages, some of these mistakes are natural pitfalls. Others are caused by false assumptions and lack of detail.

If you take the time to learn the language, read official notes, wiki s, mailing list discussions, lots of great blogs, Rob Pike's presentations, and source code, most of these tips are obvious. Although not everyone starts learning in this way, it doesn't matter. If you are new to Go, the information here will save you a lot of time debugging your code.

catalog

  • Primary section

  • Open braces cannot be placed on a single line

  • Unused variables

  • Unused Imports

  • A simple variable declaration can only be used inside a function

  • Repeat variable declaration with simple declaration

  • Accidental variable hiding

  • Variable cannot be initialized with 'nil' without explicit type

  • Using "nil" Slices and Maps

  • Map capacity

  • String will not be 'nil'

  • Parameters of Array function

  • Unexpected value when Slice and Array use the "range" statement

  • Slices and Arrays are one-dimensional

  • Access to nonexistent Map Keys

  • Strings cannot be modified

  • Conversion between String and Byte Slice

  • String and index operations

  • String is not always UTF8 text

  • Length of string

  • Missing commas in multiline Slice, Array, and Map statements

  • log.Fatal and log.Panic are not just logs

  • Built in data structure operation is not synchronous

  • Iterative value of String in "range" statement

  • Use "for range" statement iteration for Map

  • Invalidation behavior in "switch" Declaration

  • Auto increase and auto decrease

  • Bitwise NOT operation

  • Differences in operational priorities

  • Structures not exported will not be encoded

  • Application exit under active Goroutines

  • Send a message to a non cached Channel and return as soon as the target receiver is ready

  • Sending to a closed Channel causes Panic

  • Using "nil" Channels

  • The receiver of the value passing method cannot modify the original value

  • Advanced

  • Turn off HTTP response

  • Close HTTP connection

  • Compare structures, arrays, slices, and maps

  • Recover from Panic

  • Update the value of reference element in Slice, Array, and Map "range" statement

  • Hide data in Slice

  • Slice's data "destroyed"

  • "Tasteless" Slices

  • Type declaration and method

  • Jump out of "for switch" and "for select" code blocks

  • Iterative variables and closures in the "for" Declaration

  • Evaluation of parameter of Defer function call

  • Executed by function call of Defer

  • Failed type assertion

  • Blocked Goroutine and resource leakage

  • Advanced

  • Use pointer to receive an instance of the value of the method

  • Update Map values

  • Values for 'nil' interfaces and 'nil' interfaces

  • Stack and heap variables

  • GOMAXPROCS, concurrent, and parallel

  • Reordering of read and write operations

  • Priority scheduling

Primary section

Open braces cannot be placed on a single line

  • level: beginner

In most other languages that use braces, you need to choose where to place them. The way to Go is different. You can thank for the automatic semicolon injection (no pre reading). Yes, there are semicolons 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!")}

Compilation error:

/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {

Valid examples:

package main

import"fmt"

func main(){  
    fmt.Println("works!")}

Unused variables

  • level: beginner

If you have unused variables, the code fails to compile. There are exceptions, of course. You must use declared variables within a function, but unused global variables are OK.

If you assign a new value to an unused variable, the code still fails 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}

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

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
}

Another option is to comment out or remove unused variables: -)

Unused Imports

  • level: beginner

If you introduce a package without using any of its functions, interfaces, structures, or variables, the code will fail to compile.

If you really need to introduce a package, you can add an underscore flag, _, as the name of the package to avoid compilation failure. The glide line marker is used for import, but not for use.

Fails:

package main

import("fmt""log""time")

func main(){}

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"

Works:

package main

import(  
    _ "fmt""log""time")var _ = log.Println

func main(){  
    _ = time.Now}

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(){}

Compile Error:

/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body

Works:

package main

var myvar =1

func main(){}

Repeat variable declaration with simple declaration

  • level: beginner

You cannot declare a variable repeatedly in a single declaration, but this is allowed in a multivariable declaration, where at least one new declaration variable is required.

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}

Compile Error:

/tmp/sandbox706333626/main.go:5:nonew variables on left side of :=

Works:

package main

func main(){  
    one :=0
    one, two :=1,2

    one,two = two,one
}

Accidental variable hiding

  • level: beginner

The syntax of short variable declaration is so convenient (especially for developers who have used dynamic languages), it is easy to regard it as a normal allocation operation. If you make this error in a new code block, there will be no compilation errors, 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)}

Even for experienced Go developers, this is a very common pitfall. The pit is easy to dig, but hard to find.

Variable cannot be initialized with 'nil' without explicit type

  • level: beginner

The "nil" flag is used to represent the "zero value" of interface, function, maps, slices, and channels. If you don't specify the type of the variable, the compiler won't be able to compile your code because it can't guess the specific type.

Fails:

package main

func main(){var x =nil//error

    _ = x
}

Compile Error:

/tmp/sandbox188239583/main.go:4:use of untyped nil

Works:

package main

func main(){var x interface{}=nil

    _ = x
}

Using "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)}

Fails:

package main

func main(){var m map[string]int
    m["one"]=1//error}

Map capacity

  • level: beginner

You can specify the capacity of the 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}

Compile Error:

/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int)for cap

String will not be 'nil'

  • level: beginner

This is an important point 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"}}

Compile Errors:

/tmp/sandbox630560459/main.go:4: cannot usenilas type stringin assignment /tmp/sandbox630560459/main.go:6: invalid operation: x ==nil(mismatched types stringandnil)

Works:

package main

func main(){var x string//defaults to "" (zero value)if x ==""{
        x ="default"}}

Parameters of Array function

-level: beginner

If you are a C or C + + developer, arrays are pointers to you. When you pass arrays to a function, the function references the same memory area so that they can modify the original data. An array in Go is a number, so when you pass an array to a function, the function gets a copy of the original array data. This will be a problem if you plan to update the array 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])}

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]}

Another option is to use slice. Even if your function gets a copy of slice, 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]}

Unexpected value when Slice and Array use the "range" statement

  • level: beginner

This happens if you use "for in" or "foreach" statements in other languages. The syntax of "range" in Go is different. It gets two values: the first is the index of the element, and the other is the data of the element.

Bad:

package main

import"fmt"

func main(){  
    x :=[]string{"a","b","c"}for v := range x {
        fmt.Println(v)//prints 0, 1, 2}}

Good:

package main

import"fmt"

func main(){  
    x :=[]string{"a","b","c"}for _, v := range x {
        fmt.Println(v)//prints a, b, c}}

Slices and Arrays are one-dimensional

  • level: beginner

It seems that Go supports multidimensional Array and Slice, but it is not. Although you can create arrays of arrays or slices of slices. For the application of numerical calculation which depends on dynamic multidimensional Array, Go is far away in performance and complexity.

You can build a dynamic multidimensional array by using a pure dimensional array, a slice of an independent slice, and a slice of a shared data slice.

If you use a one-dimensional array, you need to deal with indexes, boundary checks, and memory reallocation when the array needs to be larger.

Using "independent" slice to create a dynamic multidimensional array takes two steps. First, you need to create an external slice. Then, you need to assign each internal slice. The internal slice is independent of each other. You can increase and decrease them without affecting other internal slices.

package main

func main(){  
    x :=2
    y :=4

    table := make([][]int,x)for i:= range table {
        table[i]= make([]int,y)}}

Using slice of shared data slice to create a dynamic multidimensional array takes three steps. First, you need to create a data "container" to hold the raw data. Then you create the 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: [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>}

There have been special applications for multidimensional array and slice, but now it looks like this is a low priority feature.

Access to nonexistent Map Keys

-level: beginner

This is a trick for developers who want to get the "nil" designator (as in other languages). If the zero value of the corresponding data type is nil, the returned value will be nil, but it is different for other data types. Detecting the corresponding "zero value" can be used to determine whether the records in the map exist, but this is not always credible (for example, if the "zero value" in the binary map is false, then what do you do). The most reliable way to detect the existence of records in a given map is to check the second returned value through the access operation of the map.

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")}}

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")}}

Strings cannot be modified

  • level: beginner

An attempt to update a single character in a string variable using an index operation will fail. String is a read-only byte slice (and some extra properties). If you do need to update a string, use byte slice and convert it to string type if necessary.

Fails:

package main

import"fmt"

func main(){  
    x :="text"
    x[0]='T'

    fmt.Println(x)}

Compile Error:

/tmp/sandbox305565531/main.go:7: cannot assign to x[0]

Works:

package main

import"fmt"

func main(){  
    x :="text"
    xbytes :=[]byte(x)
    xbytes[0]='T'

    fmt.Println(string(xbytes))//prints Text}

Note that this is not the right way to update characters in a text string, because a given character may be stored in more than one byte. If you do need to update a text string, first convert it to a run slice. Even with run slice, a single character can occupy multiple rune s, such as when your character has a specific accent. This complex and fuzzy "character" nature is the reason why Go strings are represented by 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, as well as the re slice operation when the 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 transformations to avoid additional allocations (more optimizations in the todo list).

The first optimization avoids the extra allocation when [] bytekey is used to query in the map[string] collection: m[string(key)].

The second optimization avoids the extra allocation of: for I, V: = range [] byte (STR) {...} in the for range statement after the string is converted to [] byte.

String and index operations

  • level: beginner

An index operation on a string returns a byte value instead of 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}

If you need access to a specific string of "characters" (unicode encoded points / runs), use for range. The official "unicode/utf8" package and the experimental utf8string package (golang.org/x/exp/utf8string) can also be used. The utf8string package contains a convenient At() method. Also an option is to convert strings to run slices.

String is not always UTF8 text

  • level: beginner

The value of the string does not need to be the text of UTF8. They can contain any byte. The string will only be UTF8 if it is used by string literal. Even then they can use escape sequences to contain other data.

To know if the 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}

Length of string

  • level: beginner

Let's assume you are a Python developer. You have the following code:

data = u'♥'print(len(data))#prints: 1  

You may be surprised when you convert it to Go code.

package main

import"fmt"

func main(){  
    data :="♥"
    fmt.Println(len(data))//prints: 3}

The built-in len() function returns the number of byte s, rather than the number of characters in a calculated unicode string like Python.

To get the same results 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}

In theory, the RuneCountInString() function does not return the number of characters, because a single character may occupy multiple runes.

package main

import("fmt""unicode/utf8")

func main(){  
    data :="é"
    fmt.Println(len(data))//prints: 3
    fmt.Println(utf8.RuneCountInString(data))//prints: 2}

Missing commas in multiline Slice, Array, and Map statements

  • level: beginner

Fails:

package main

func main(){  
    x :=[]int{1,2//error}
    _ = x
}

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 }

Works:

package main

func main(){  
    x :=[]int{1,2,}
    x = x

    y :=[]int{3,4,}//no error
    y = y
}

When you collapse a declaration to a single line, you won't get compilation errors if you don't add a comma at the end.

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")}

Built in data structure operation is not synchronous

  • level: beginner

Even if Go itself has many features to support concurrency, concurrent and secure data set consolidation is not one of them: -) it's your job 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.

Iterative value of String in "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 unicode point / run of the second value returned. It is not the index of the current "character", which is different from other languages. Note that real characters may be represented by multiple rune s. If you need to process 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 cannot understand, it will return 0xfffd runs (that is, unicode replacement characters), rather than real data. If you save any (non UTF8 text) data in a string variable, make 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)}

Use "for range" statement iteration for Map

  • level: beginner

You need this technique if you want to get elements in a certain order (for example, by key value). Each map iteration will produce different results. Go's runtime tries to randomize the iteration sequence, but it doesn't always succeed, so you may get some of the same map iteration results. So don't be surprised if you see five identical iterations 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)}}

And if you use Go's playground( https://play.golang.org/ )You'll always get the same result, because it won't recompile code unless you modify it.

Invalidation behavior in "switch" Declaration

  • level: beginner

The 'case' block in the 'switch' declaration statement break s by default. This is different from the default behavior of entering the next "next" block in other languages.

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)}

You can force "case" code blocks 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)}

Auto increase and auto decrease

  • level: beginner

Many languages have auto increment and auto decrement operations. Unlike other languages, Go does not support pre version operations. You can't use these two operators in expressions either.

Fails:

package main

import"fmt"

func main(){  
    data :=[]int{1,2,3}
    i :=0++i //error
    fmt.Println(data[i++])//error}

Compile Errors:

/tmp/sandbox101231828/main.go:8: syntax error: unexpected ++/tmp/sandbox101231828/main.go:9: syntax error: unexpected ++, expecting :

Works:

package main

import"fmt"

func main(){  
    data :=[]int{1,2,3}
    i :=0
    i++
    fmt.Println(data[i])}

Bitwise NOT operation

  • level: beginner

Many languages use ~ as a NOT operator (that is, bitwise complement), but Go reuses the XOR operator (^) for this.

Fails:

package main

import"fmt"

func main(){  
    fmt.Println(~2)//error}

Compile Error:

/tmp/sandbox965529189/main.go:6: the bitwise complement operatoris^

Works:

package main

import"fmt"

func main(){var d uint8 =2
    fmt.Printf("%08b\n",^d)}

Go still uses ^ as the XOR operator, which may confuse some people.

If you like, you can use a binary XOR operation (for example, 0x02 XOR 0xff) to represent a unary NOT operation (for example, NOT 0x02). This explains why ^ is reused to represent unary NOT operations.

Go also has a special "AND NOT" bitwise operation (& ^), which also makes NOT operation 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))}

Differences in operational priorities

  • level: beginner

In addition to the "bit clear" operation (& ^), Go is also a collection of standard operators shared with many other languages. Although the priority of operations is 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}

Structures not exported will not be encoded

  • level: beginner

Structures that start with lowercase letters will not be encoded (json, xml, gob, etc.), so when you encode these non exported structures, you will get a zero value.

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:""}}

Application exit under active Goroutines

  • level: beginner

The application will not have to be completed with all goroutines. This is a common mistake for beginners. Everyone starts at a certain level, so it's not shameful to make a beginner's mistake: -)

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)}

You will see:

[0]is running
[1]is running
all done!

One of the most common solutions is to use the "WaitGroup" variable. It will let the main goroutine wait for all workers to complete. If your application has a worker with a long-running message processing loop, you will also need a way to signal these goroutines to exit. You can send a "kill" message to each worker. Another option is to turn off a channel that all workers receive. This is a simple way to signal 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)}

If you run the app, you will see:

[0]is running
[0]isdone[1]is running
[1]isdone

It seems that all worker s are finished before the main goroutine exits. Great! However, you will also see this:

fatal error: all goroutines are asleep - deadlock!

This is not very good: -) sent Shenma? Why deadlock? Workers quit, and they execute wg.Done(). There should be no problem with the application.

Deadlock occurs 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}}}

Now it will work as expected: -)

Send a message to a non cached Channel and return 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 you are running the code on, the receiver's goroutine may or may not have enough time to process the message before the sender continues execution.

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}

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, which means no data is received. If you receive from a channel with cache, you will get the cached data first. Once it is empty, the returned ok value will be false.

Sending data to a closed channel causes panic. This behavior is documented, but new Go developers have different intuitions, and they may want the sending behavior to be similar to 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)}

According to different applications, the repair methods will be different. It may be a small code change, or it may be necessary to change the design of the application. Either way, you need to make sure your app doesn't send data to a closed channel.

The bug example above can be fixed by using a special abandoned channel to signal the remaining worker s that they no longer need their results.

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)}

Using "nil" Channels

  • level: beginner

Send and receive operations on a nil channel are permanently blocked. This behavior is well documented, but it's a surprise for 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)}

If you run the code you will see a runtime error:

fatal error: all goroutines are asleep - deadlock!

This behavior can be used in the select declaration to dynamically turn on and off 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)}

The receiver of the value passing method cannot modify the original value

  • level: beginner

The receiver of a method is like a regular function parameter. If declared as a value, your function / method gets a copy of the recipient parameter. 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 the elements in the collection, or the receiver of the domain 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]}

Original address: levy.at/blog/11

Posted by immanuelx2 on Mon, 18 May 2020 08:06:01 -0700