1.开头大括号不能放在单独的行上
- level: beginner
其实,和C一样,Go的正式的语法使用分号来终止语句。和C不同的是,这些分号由词法分析器在扫描源代码过程中使用简单的规则自动插入分号,因此输入源代码多数时候就不需要分号了。 无论任何时候,你都不应该将一个控制结构((if、for、switch或select)的左大括号放在下一行。如果这样做,将会在大括号的前方插入一个分号,否则编译报错。
package main import "fmt" func main() { //error, 开头大括号不能独立一行 fmt.Println("hello there!") }
Compile Error:
/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {
package main import "fmt" func main() { fmt.Println("works!") }
2.未使用的变量
- level: beginner
如果您有未使用的变量,您的代码将无法编译。但是也有例外: 1. 有未使用的全局变量,2. 有未使用的函数参数。
如果将新值分配给未使用的变量,则代码仍将无法编译
package main var gvar int //not an error func main() { var one int //error, unused variable two := 2 //error, unused variable var three int //error, even though it's assigned 3 on the next line three = 3 func(unused string) { fmt.Println("Unused arg. No compile error") }("what?") }
Compile Errors:
/tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used
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 }
3.未使用的导入
Unused Import
- level: beginner
如果在不使用任何导出函数,接口,结构或变量的情况下导入软件包,则代码将无法编译。
如果确实需要导入的软件包,则可以使用空白标识符_作为其软件包名称,以避免此编译失败。
package main
import (
"fmt"
"log"
"time"
)
func main() {
}
Compile Errors:
/tmp/sandbox627475386/main.go:4: imported and not used: “fmt” /tmp/sandbox627475386/main.go:5: imported and not used: “log” /tmp/sandbox627475386/main.go:6: imported and not used: “time”
package main import ( _ "fmt" "log" "time" ) var _ = log.Println func main() { _ = time.Now }
4.简短的变量声明只能在函数内部使用
Short Variable Declarations Can Be Used Only Inside Functions
- level: beginner
package main myvar := 1 //error func main() { }
Compile Error:
/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
package main var myvar = 1 func main() { }
5.使用短变量声明重新声明变量
Redeclaring Variables Using Short Variable Declarations
- level: beginner
您无法在独立的语句中重新声明变量,但是在多变量声明中允许使用该变量,其中至少还声明了一个新变量。
重新声明的变量必须位于同一块中,否则最终将得到阴影变量。
package main
func main() {
one := 0
one := 1 //error
}
Compile Error:
/tmp/sandbox706333626/main.go:5: no new variables on left side of :=
package main func main() { one := 0 one, two := 1,2 one,two = two,one }
6.不能使用短变量声明来设置字段值(比如结构体的字段)
Can’t Use Short Variable Declarations to Set Field Values
- level: beginner
package main import ( "fmt" ) type info struct { result int } func work() (int,error) { return 13,nil } func main() { var data info data.result, err := work() //error fmt.Printf("info: %+v\n",data) }
Compile Error:
prog.go:18: non-name data.result on left side of :=
Even though there’s a ticket to address this gotcha it’s unlikely to change because Rob Pike likes it “as is” 🙂
Use temporary variables or predeclare all your variables and use the standard assignment operator.
package main import ( "fmt" ) type info struct { result int } func work() (int,error) { return 13,nil } func main() { var data info var err error data.result, err = work() //ok if err != nil { fmt.Println(err) return } fmt.Printf("info: %+v\n",data) //prints: info: {result:13} }
7.可能出现的“变量阴影”
Accidental Variable Shadowing
- level: beginner
简短的变量声明语法非常方便(尤其是对于来自动态语言初学者写的代码),因此很容易将其视为常规赋值操作。
如果在新的代码块中犯了此错误,将不会有编译器错误,但应用程序将无法得到期望结果。
参考:go变量作用域
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 (但实际上,你可能期望得到 2) }
即使对于有经验的Go开发人员来说,这也是一个非常常见的陷阱,并且很容易触发,而且很难发现。
可以使用vet
命令来查找其中的一些问题。默认情况下,vet
不执行任何阴影变量检查, 如需检查加上-shadow
选项
go tool vet -shadow your_file.go
值得注意的是vet
不会报出全部的 shadowed variables
, 使用go-nyet
是一个更好的 shadowed variables
检测工具。
8.不能使用“nil”来初始化没有显式类型的变量 var
Can’t Use “nil” to Initialize a Variable Without an Explicit Type
- level: beginner
The “nil” identifier can be used as the “zero value” for interfaces, functions, pointers, maps, slices, and channels. If you don’t specify the variable type the compiler will fail to compile your code because it can’t guess the type.
package main
func main() {
var x = nil //error
_ = x
}
Compile Error:
/tmp/sandbox188239583/main.go:4: use of untyped nil
package main func main() { var x interface{} = nil _ = x }
9.不能直接使用nil
值的Slice和Map-- 只是声明没有分配内存空间不能直接赋值是
- level: beginner
It’s OK to add items to a “nil” slice, but doing the same with a map will produce 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 }
10.map使用make分配内存时可指定capicity,但是不能对map使用cap函数
- level: beginner
You can specify the map capacity when it’s created, but you can’t use the cap()
function on maps.
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
11.字符串不允许使用nil
值
在golang中,nil只能赋值给指针、channel、func、interface、map或slice类型的变量。
- level: beginner
This is a gotcha for developers who are used to assigning “nil” identifiers to string variables.
Fails:
package main func main() { var x string = nil //error if x == nil { //error x = "default" } }
Compile Errors:
/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)
Works:
package main func main() { var x string //defaults to "" (zero value) if x == "" { x = "default" } }
12.数组用于函数传参时是值复制
注意:方法或函数调用时,传入参数都是值复制(跟赋值一致),除非是map、slice、channel、指针类型这些特殊类型是引用传递。
- level: beginner
If you are a C or C++ developer arrays for you are pointers. When you pass arrays to functions the functions reference the same memory location, so they can update the original data. Arrays in Go are values, so when you pass arrays to functions the functions get a copy of the original array data. This can be a problem if you are trying 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 original array data use array pointer types.
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 slices. Even though 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]
}
13.range关键字返回是键值对,而不是值
- level: beginner
This can happen if you are used to the “for-in” or “foreach” statements in other languages. The “range” clause in Go is different. It generates two values: the first value is the item index while the second value is the item data.
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
}
}
14.Slice和Array是一维的
Go表面上看起来像多维的,实际上只是一维的。但可以使用原始的一维数组、一维的切片来实现多维。
- level: beginner
It may seem like Go supports multi-dimensional arrays and slices, but it doesn’t. Creating arrays of arrays or slices of slices is possible though. For numerical computation apps that rely on dynamic multi-dimensional arrays it’s far from ideal in terms of performance and complexity.
You can build dynamic multi-dimensional arrays using raw one-dimensional arrays, slices of “independent” slices, and slices of “shared data” slices.
If you are using raw one-dimensional arrays you are responsible for indexing, bounds checking, and memory reallocations when the arrays need to grow.
Creating a dynamic multi-dimensional array using slices of “independent” slices is a two step process. First, you have to create the outer slice. Then, you have to allocate each inner slice. The inner slices are independent of each other. You can grow and shrink them without affecting other inner slices.
package main
func main() {
x := 2
y := 4
table := make([][]int,x)
for i:= range table {
table[i] = make([]int,y)
}
}
Creating a dynamic multi-dimensional array using slices of “shared data” slices is a three step process. First, you have to create the data “container” slice that will hold raw data. Then, you create the outer slice. Finally, you initialize each inner slice by reslicing the raw 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’s a spec/proposal for multi-dimensional arrays and slices, but it looks like it’s a low priority feature at this point in time.
15.从不存在key的map中取值时,返回的总是”0值”
- level: beginner
This is a gotcha for developers who expect to get “nil” identifiers (like it’s done in other languages). The returned value will be “nil” if the “zero value” for the corresponding data type is “nil”, but it’ll be different for other data types. Checking for the appropriate “zero value” can be used to determine if the map record exists, but it’s not always reliable (e.g., what do you do if you have a map of booleans where the “zero value” is false). The most reliable way to know if a given map record exists is to check the second value returned by the map 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")
}
}
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") } }
16.字符串是不可变
- level: beginner
Trying to update an individual character in a string variable using the index operator will result in a failure. Strings are read-only byte slices (with a few extra properties). If you do need to update a string then use a byte slice instead converting it to a string type when 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 isn’t really the right way to update characters in a text string because a given character could be stored in multiple bytes. If you do need to make updates to a text string convert it to a rune slice first. Even with rune slices a single character might span multiple runes, which can happen if you have characters with grave accent, for example. This complicated and ambiguous nature of “characters” is the reason why Go strings are represented as byte sequences.
17.字符串与[]byte
之间的转换是复制(有内存损耗),可以用map[string] []byte建立字符串与[]byte之间映射,也可range来避免内存分配来提高性能
- level: beginner
When you convert a string to a byte slice (and vice versa) you get a complete copy of the orginal data. It’s not like a cast operation in other languages and it’s not like reslicing where the new slice variable points to the same underlying array used by the original byte slice.
Go does have a couple of optimizations for []byte
to string
and string
to []byte
conversions to avoid extra allocations (with more optimizations on the todo list).
The first optimization avoids extra allocations when []byte
keys are used to lookup entries in map[string]
collections: m[string(key)]
.
The second optimization avoids extra allocations in for range
clauses where strings are converted to []byte
: for i,v := range []byte(str) {...}
.
//[]byte: for i,v := range []byte(str) { }
18.string的索引操作返回的是byte(或uint8),如想获取字符可使用for range
,也可使用unicode/utf8
包和golang.org/x/exp/utf8string
包的At()
方法。
- level: beginner
The index operator on a string returns a byte value, not a character (like it’s done 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 to access specific string “characters” (unicode code points/runes) use the for range
clause. The official “unicode/utf8” package and the experimental utf8string package (golang.org/x/exp/utf8string) are also useful. The utf8string package includes a convenient At()
method. Converting the string to a slice of runes is an option too.
19.字符串并不总是UTF8的文本
- level: beginner
String values are not required to be UTF8 text. They can contain arbitrary bytes. The only time strings are UTF8 is when string literals are used. Even then they can include other data using escape sequences.
To know if you have a UTF8 text string use the ValidString()
function from 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
}
20.len(str)
返回的是字符串的字节数,获取字符串的rune数是使用unicode/utf8.RuneCountInString()
函数,但注意一些字符也可能是由多个rune组成,如é
是两个rune组成。
- level: beginner
Let’s say you are a python developer and you have the following piece of code:
data = u'♥' print(len(data)) #prints: 1
When you convert it to a similar Go code snippet you might be surprised.
package main import "fmt" func main() { data := "♥" fmt.Println(len(data)) //prints: 3 }
The built-in len()
function returns the number of bytes instead of the number of characters like it’s done for unicode strings in Python.
To get the same results in Go use the RuneCountInString()
function from the “unicode/utf8” package.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := "♥"
fmt.Println(utf8.RuneCountInString(data)) //prints: 1
Technically the RuneCountInString()
function doesn’t return the number of characters because a single character may span multiple runes.
package main import ( "fmt" "unicode/utf8" ) func main() { data := "é" fmt.Println(len(data)) //prints: 3 fmt.Println(utf8.RuneCountInString(data)) //prints: 2 }
21.在Slice、Array、Map的多行书写最后的逗号不可省略
- 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 }
You won’t get a compiler error if you leave the trailing comma when you collapse the declaration to be on a single line.
22.log包中的log.Fata
l和log.Panic
不仅仅记录日志,还会中止程序。它不同于Logging
库。
- level: beginner
Logging libraries often provide different log levels. Unlike those logging libraries, the log package in Go does more than log if you call its Fatal*()
and Panic*()
functions. When your app calls those functions Go will also terminate your app 🙂
package main import "log" func main() { log.Fatalln("Fatal Level: log entry") //app exits here log.Println("Normal Level: log entry") }
23.内置数据结构的操作并不同步,但可把Go提供了并发的特性使用起来:goroutines和channels。
- level: beginner
Even though Go has a number of features to support concurrency natively, concurrency safe data collections are not one them 🙂 It’s your responsibility to ensure the data collection updates are atomic. Goroutines and channels are the recommended way to implement those atomic operations, but you can also leverage the “sync” package if it makes sense for your application.
24.使用for range迭代String,是以rune来迭代的。
一个字符,也可以有多个rune组成。需要处理字符,尽量使用golang.org/x/text/unicode/norm
包。
for range总是尝试将字符串解析成utf8的文本,对于它无法解析的字节,它会返回oxfffd
的rune字符。
因此,任何包含非utf8的文本,一定要先将其转换成字符切片([]byte
)。
- level: beginner
The index value (the first value returned by the “range” operation) is the index of the first byte for the current “character” (unicode code point/rune) returned in the second value. It’s not the index for the current “character” like it’s done in other languages. Note that an actual character might be represented by multiple runes. Make sure to check out the “norm” package (golang.org/x/text/unicode/norm) if you need to work with characters.
The for range
clauses with string variables will try to interpret the data as UTF8 text. For any byte sequences it doesn’t understand it will return 0xfffd runes (aka unicode replacement characters) instead of the actual data. If you have arbitrary (non-UTF8 text) data stored in your string variables, make sure to convert them to byte slices to get all stored data as is.
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)
}
25.使用for range迭代map时每次迭代的顺序可能不一样,因为map的迭代是随机的。
- level: beginner
This is a gotcha if you expect the items to be in a certain order (e.g., ordered by the key value). Each map iteration will produce different results. The Go runtime tries to go an extra mile randomizing the iteration order, but it doesn’t always succeed so you may get several identical map iterations. Don’t be surprised to see 5 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 the Go Playground (https://play.golang.org/) you’ll always get the same results because it doesn’t recompile the code unless you make a change.
26.switch的case默认匹配规则不同于其它语言的是,匹配case条件后默认退出,除非使用fallthrough继续匹配;而其它语言是默认继续匹配,除非使用break退出匹配。
- level: beginner
The “case” blocks in “switch” statements break by default. This is different from other languages where the default behavior is to fall through to the next “case” block.
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch(ch) {
case ' ': //error
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) //prints true (ok)
fmt.Println(isSpace(' ')) //prints false (not ok)
}
You can force the “case” blocks to fall through by using the “fallthrough” statement at the end of each “case” block. You can also rewrite your switch statement to use expression lists in the “case” blocks.
package main import "fmt" func main() { isSpace := func(ch byte) bool { switch(ch) { case ' ', '\t': return true } return false } fmt.Println(isSpace('\t')) //prints true (ok) fmt.Println(isSpace(' ')) //prints true (ok) }
27.只有后置自增、后置自减,不存在前置自增、前置自减
- level: beginner
Many languages have increment and decrement operators. Unlike other languages, Go doesn’t support the prefix version of the operations. You also can’t use these two operators in expressions.
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]) }
28.位运算的非操作是^
(跟异或位运算一样),有别于其它语言的~
。
- level: beginner
Many languages use ~
as the unary NOT operator (aka bitwise complement), but Go reuses the XOR operator (^
) for that.
Fails:
package main import "fmt" func main() { fmt.Println(~2) //error }
Compile Error:
/tmp/sandbox965529189/main.go:6: the bitwise complement operator is ^
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 be confusing for some people.
If you want you can represent a unary NOT operation (e.g, NOT 0x02
) with a binary XOR operation (e.g., 0x02 XOR 0xff
). This could explain why ^
is reused to represent unary NOT operations.
Go also has a special ‘AND NOT’ bitwise operator (&^
), which adds to the NOT operator confusion. It looks like a special feature/hack to support A AND (NOT B)
without requiring parentheses.
package main import "fmt" func main() { var a uint8 = 0x82 var b uint8 = 0x02 fmt.Printf("%08b [A]\n",a) fmt.Printf("%08b [B]\n",b) fmt.Printf("%08b (NOT B)\n",^b) fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n",b,0xff,b ^ 0xff) fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n",a,b,a ^ b) fmt.Printf("%08b & %08b = %08b [A AND B]\n",a,b,a & b) fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n",a,b,a &^ b) fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n",a,b,a & (^b)) }
29.位运算(与、或、异或、取反)优先级高于四则运算(加、减、乘、除、取余),有别于C语言。
- level: beginner
Aside from the “bit clear” operators (&^
) Go has a set of standard operators shared by many other languages. The operator precedence is not always the same though.
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 }
30.结构体在序列化时非导出字段(以小写字母开头的字段名)不会被encode,因此在decode时这些非导出字段的值为”0值”
- level: beginner
The struct fields starting with lowercase letters will not be (json, xml, gob, etc.) encoded, so when you decode the structure you’ll end up with zero values in those unexported fields.
package main import ( "fmt" "encoding/json" ) type MyData struct { One int 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} var out MyData json.Unmarshal(encoded,&out) fmt.Printf("%#v\n",out) //prints main.MyData{One:1, two:""} }
31.程序不等所有goroutine结束就会退出。可通过channel实现主协程(main goroutine)等待所有goroutine完成。
- level: beginner
The app will not wait for all your goroutines to complete. This is a common mistake for beginners in general. Everybody starts somewhere, so there’s no shame in making rookie mistakes 🙂
package main import ( "fmt" "time" ) func main() { workerCount := 2 for i := 0; i < workerCount; i++ { go doit(i) } time.Sleep(1 * time.Second) fmt.Println("all done!") } func doit(workerId int) { fmt.Printf("[%v] is running\n",workerId) time.Sleep(3 * time.Second) fmt.Printf("[%v] is done\n",workerId) }
You’ll see:
[0] is running [1] is running all done!
One of the most common solutions is to use a “WaitGroup” variable. It will allow the main goroutine to wait until all worker goroutines are done. If your app has long running workers with message processing loops you’ll also need a way to signal those goroutines that it’s time to exit. You can send a “kill” message to each worker. Another option is to close a channel all workers are receiving from. It’s a simple way to signal all goroutines at once.
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup done := make(chan struct{}) workerCount := 2 for i := 0; i < workerCount; i++ { wg.Add(1) go doit(i,done,wg) } close(done) wg.Wait() fmt.Println("all done!") } func doit(workerId int,done <-chan struct{},wg sync.WaitGroup) { fmt.Printf("[%v] is running\n",workerId) defer wg.Done() <- done fmt.Printf("[%v] is done\n",workerId) }
If you run this app you’ll see:
[0] is running [0] is done [1] is running [1] is done
Looks like the workers are done before the main goroutine exits. Great! However, you’ll also see this:
fatal error: all goroutines are asleep - deadlock!
That’s not so great 🙂 What’s going on? Why is there a deadlock? The workers exited and they executed wg.Done()
. The app should work.
The deadlock happens because each worker gets a copy of the original “WaitGroup” variable. When workers execute wg.Done()
it has no effect on the “WaitGroup” variable in the main goroutine.
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup done := make(chan struct{}) wq := make(chan interface{}) workerCount := 2 for 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 works as expected 🙂
32.对于无缓存区的channel,写入channel的goroutine会阻塞直到被读取,读取channel的goroutine会阻塞直到有数据写入。
- level: beginner
The sender will not be blocked until your message is processed by the receiver. Depending on the machine where you are running the code, the receiver goroutine may or may not have enough time to process the message before the sender continues its 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 }
33.向一个nil
值(未用make分配空间)的channel发送或读取数据,会导致永远阻塞。
- level: beginner
Receiving from a closed channel is safe. The ok
return value in a receive statement will be set to false
indicating that no data was received. If you are receiving from a buffered channel you’ll get the buffered data first and once it’s empty the ok
return value will be false
.
Sending data to a closed channel causes a panic. It is a documented behavior, but it’s not very intuitive for new Go developers who might expect the send behavior to be similar to the receive 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) }
Depending on your application the fix will be different. It might be a minor code change or it might require a change in your application design. Either way, you’ll need to make sure your application doesn’t try to send data to a closed channel.
The buggy example can be fixed by using a special cancellation channel to signal the remaining workers that their results are no longer neeeded.
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) }
34.向一个nil
值(未用make分配空间)的channel发送或读取数据,会导致永远阻塞。
- level: beginner
Send and receive operations on a nil
channel block forver. It’s a well documented behavior, but it can be a surprise for new Go developers.
package main import ( "fmt" "time" ) func main() { var ch chan int for 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’ll see a runtime error like this: fatal error: all goroutines are asleep - deadlock!
This behavior can be used as a way to dynamically enable and disable case
blocks in a select
statement.
package main import "fmt" import "time" func main() { inch := make(chan int) outch := make(chan int) go func() { var in <- chan int = inch var out chan <- int var val int for { select { case out <- val: out = nil in = 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) }
35.方法接收者是类型(T),接收者只是原对象的值复制,在方法中修改接收者不会修改原始对象的值;如果方法接收者是指针类型(*T),是对原对象的引用,方法中对其修改当然是原对象修改。
- level: beginner
Method receivers are like regular function arguments. If it’s declared to be a value then your function/method gets a copy of your receiver argument. This means making changes to the receiver will not affect the original value unless your receiver is a map or slice variable and you are updating the items in the collection or the fields you are updating in the receiver are pointers
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] }
36.关闭HTTP的Response.Body
使用defer语句关闭资源时要注意nil值,在defer语句之前要进行nill值处理
以下以http包的使用为例
- level: intermediate
When you make requests using the standard http library you get a http response variable. If you don’t read the response body you still need to close it. Note that you must do it for empty responses too. It’s very easy to forget especially for new Go developers.
Some new Go developers do try to close the response body, but they do it in the wrong place.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() { resp, err := http.Get("https://api.ipify.org?format=json") defer resp.Body.Close()//not ok if err != nil { fmt.Println(err) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body)) }
This code works for successful requests, but if the http request fails the resp
variable might be nil
, which will cause a runtime panic.
The most common why to close the response body is by using a defer
call after the http response error check.
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()//ok, most of the time :-)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
Most of the time when your http request fails the resp
variable will be nil
and the err
variable will be non-nil
. However, when you get a redirection failure both variables will be non-nil
. This means you can still end up with a leak.
You can fix this leak by adding a call to close non-nil
response bodies in the http response error handling block. Another option is to use one defer
call to close response bodies for all failed and successful requests.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() { resp, err := http.Get("https://api.ipify.org?format=json") if resp != nil { defer resp.Body.Close() } if err != nil { fmt.Println(err) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body)) }
在Go 1.5之前resp.Body.Close()
会读取并丢失body中的数据,保证在启用keepaliva的http时能够在下一次请求时重用。
在Go 1.5之后,就需要在关闭前手动处理。
The orignal implementation for resp.Body.Close()
also reads and discards the remaining response body data. This ensured that the http connection could be reused for another request if the keepalive http connection behavior is enabled. The latest http client behavior is different. Now it’s your responsibility to read and discard the remaining response data. If you don’t do it the http connection might be closed instead of being reused. This little gotcha is supposed to be documented in Go 1.5.
- 如果只是读取Body的部分,就很有必要在关闭Body之前做这种手动处理。例如处理json api响应时
json.NewDecoder(resp.Body).Decode(&data)
就需要处理掉剩余的数据。
If reusing the http connection is important for your application you might need to add something like this at the end of your response processing logic:
1
_, err = io.Copy(ioutil.Discard, resp.Body)
It will be necessary if you don’t read the entire response body right away, which might happen if you are processing json API responses with code like this:
1
json.NewDecoder(resp.Body).Decode(&data)
Closing HTTP Connections
- level: intermediate
Some HTTP servers keep network connections open for a while (based on the HTTP 1.1 spec and the server “keep-alive” configurations). By default, the standard http library will close the network connections only when the target HTTP server asks for it. This means your app may run out of sockets/file descriptors under certain conditions.
You can ask the http library to close the connection after your request is done by setting the Close
field in the request variable to true
.
Another option is to add a Connection
request header and set it to close
. The target HTTP server should respond with a Connection: close
header too. When the http library sees this response header it will also close the connection.
|
|
You can also disable http connection reuse globally. You’ll need to create a custom http transport configuration for it.
|
|
If you send a lot of requests to the same HTTP server it’s ok to keep the network connection open. However, if your app sends one or two requests to many different HTTP servers in a short period of time it’s a good idea to close the network connections right after your app receives the responses. Increasing the open file limit might be a good idea too. The correct solution depends on your application though.
JSON Encoder Adds a Newline Character
- level: intermediate
You are writing a test for your JSON encoding function when you discover that your tests fail because you are not getting the expected value. What happened? If you are using the JSON Encoder object then you’ll get an extra newline character at the end of your encoded JSON object.
|
|
The JSON Encoder object is designed for streaming. Streaming with JSON usually means newline delimited JSON objects and this is why the Encode method adds a newline character. This is a documented behavior, but it’s commonly overlooked or forgotten.
JSON Package Escapes Special HTML Characters in Keys and String Values
- level: intermediate
This is a documented behavior, but you have to be careful reading all of the JSON package documentation to learn about it. The SetEscapeHTML
method description talks about the default encoding behavior for the and, less than and greater than characters.
This is a very unfortunate design decision by the Go team for a number of reasons. First, you can’t disable this behavior for the json.Marshal
calls. Second, this is a badly implemented security feature because it assumes that doing HTML encoding is sufficient to protect against XSS vulnerabilities in all web applications. There are a lot of different contexts where the data can be used and each context requires its own encoding method. And finally, it’s bad because it assumes that the primary use case for JSON is a web page, which breaks the configuration libraries and the REST/HTTP APIs by default.
|
|
A suggestion to the Go team… Make it an opt-in.
Unmarshalling JSON Numbers into Interface Values
- level: intermediate
By default, Go treats numeric values in JSON as float64
numbers when you decode/unmarshal JSON data into an interface. This means the following code will fail with a panic:
|
|
Runtime Panic:
panic: interface conversion: interface is float64, not int
If the JSON value you are trying to decode is an integer you have serveral options.
Option one: use the float value as-is 🙂
Option two: convert the float value to the integer type you need.
|
|
Option three: use a Decoder
type to unmarshal JSON and tell it to represent JSON numbers using the Number
interface type.
|
|
You can use the string representation of your Number
value to unmarshal it to a different numeric type:
|
|
Option four: use a struct
type that maps your numeric value to the numeric type you need.
|
|
Option five: use a struct
that maps your numeric value to the json.RawMessage
type if you need to defer the value decoding.
This option is useful if you have to perform conditional JSON field decoding where the field type or structure might change.
|
|
JSON String Values Will Not Be Ok with Hex or Other non-UTF8 Escape Sequences
- level: intermediate
Go expects string values to be UTF8 encoded. This means you can’t have arbitrary hex escaped binary data in your JSON strings (and you also have to escape the backslash character). This is really a JSON gotcha Go inherited, but it happens often enough in Go apps that it makes sense to mention it anyways.
|
|
The Unmarshal/Decode calls will fail if Go sees a hex escape sequence. If you do need to have a backslash in you string make sure to escape it with another backlash. If you want to use hex encoded binary data you can escape the backslash and then do your own hex escaping with the decoded data in your JSON string.
|
|
Another option is to use the byte array/slice data type in your JSON object, but the binary data will have to be base64 encoded.
|
|
Something else to watch out for is the Unicode replacement character (U+FFFD). Go will use the replacement character instead of invalid UTF8, so the Unmarshal/Decode call will not fail, but the string value you get might not be what you expected.
Comparing Structs, Arrays, Slices, and Maps
- level: intermediate
You can use the equality operator, ==
, to compare struct variables if each structure field can be compared with the equality operator.
|
|
If any of the struct fields are not comparable then using the equality operator will result in compile time errors. Note that arrays are comparable only if their data items are comparable.
|
|
Go does provide a number of helper functions to compare variables that can’t be compared using the comparison operators.
The most generic solution is to use the DeepEqual()
function in the reflect package.
|
|
Aside from being slow (which may or may not be a deal breaker for your application), DeepEqual()
also has its own gotchas.
|
|
DeepEqual()
doesn’t consider an empty slice to be equal to a “nil” slice. This behavior is different from the behavior you get using the bytes.Equal()
function. bytes.Equal()
considers “nil” and empty slices to be equal.
|
|
DeepEqual()
isn’t always perfect comparing slices.
|
|
If your byte slices (or strings) contain text data you might be tempted to use ToUpper()
or ToLower()
from the “bytes” and “strings” packages when you need to compare values in a case insensitive manner (before using ==
,bytes.Equal()
, or bytes.Compare()
). It will work for English text, but it will not work for text in many other languages. strings.EqualFold()
and bytes.EqualFold()
should be used instead.
If your byte slices contain secrets (e.g., cryptographic hashes, tokens, etc.) that need to be validated against user-provided data, don’t use reflect.DeepEqual()
, bytes.Equal()
, or bytes.Compare()
because those functions will make your application vulnerable to timing attacks. To avoid leaking the timing information use the functions from the ‘crypto/subtle’ package (e.g., subtle.ConstantTimeCompare()
).
Recovering From a Panic
- level: intermediate
The recover()
function can be used to catch/intercept a panic. Calling recover()
will do the trick only when it’s done in a deferred function.
Incorrect:
|
|
Works:
|
|
The call to recover()
works only if it’s called directly in your deferred function.
Fails:
|
|
Updating and Referencing Item Values in Slice, Array, and Map “range” Clauses
- level: intermediate
The data values generated in the “range” clause are copies of the actual collection elements. They are not references to the original items. This means that updating the values will not change the original data. It also means that taking the address of the values will not give you pointers to the original data.
|
|
If you need to update the original collection record value use the index operator to access the data.
|
|
If your collection holds pointer values then the rules are slightly different. You still need to use the index operator if you want the original record to point to another value, but you can update the data stored at the target location using the second value in the “for range” clause.
|
|
“Hidden” Data in Slices
- level: intermediate
When you reslice a slice, the new slice will reference the array of the original slice. If you forget about this behavior it can lead to unexpected memory usage if your application allocates large temporary slices creating new slices from them to refer to small sections of the original data.
|
|
To avoid this trap make sure to copy the data you need from the temporary slice (instead of reslicing it).
|
|
Slice Data “Corruption”
- level: intermediate
Let’s say you need to rewrite a path (stored in a slice). You reslice the path to reference each directory modifying the first folder name and then you combine the names to create a new path.
|
|
It didn’t work as you expected. Instead of “AAAAsuffix/BBBBBBBBB” you ended up with “AAAAsuffix/uffixBBBB”. It happened because both directory slices referenced the same underlying array data from the original path slice. This means that the original path is also modified. Depending on your application this might be a problem too.
This problem can fixed by allocating new slices and copying the data you need. Another option is to use the full slice expression.
|
|
The extra parameter in the full slice expression controls the capacity for the new slice. Now appending to that slice will trigger a new buffer allocation instead of overwriting the data in the second slice.
“Stale” Slices
- level: intermediate
Multiple slices can reference the same data. This can happen when you create a new slice from an existing slice, for example. If your application relies on this behavior to function properly then you’ll need to worry about “stale” slices.
At some point adding data to one of the slices will result in a new array allocation when the original array can’t hold any more new data. Now other slices will point to the old array (with old data).
|
|
Type Declarations and Methods
- level: intermediate
When you create a type declaration by defining a new type from an existing (non-interface) type, you don’t inherit the methods defined for that existing type.
Fails:
|
|
Compile Errors:
/tmp/sandbox106401185/main.go:9: mtx.Lock undefined (type myMutex has no field or method Lock) /tmp/sandbox106401185/main.go:10: mtx.Unlock undefined (type myMutex has no field or method Unlock)
If you do need the methods from the original type you can define a new struct type embedding the original type as an anonymous field.
Works:
|
|
Interface type declarations also retain their method sets.
Works:
|
|
Breaking Out of “for switch” and “for select” Code Blocks
- level: intermediate
A “break” statement without a label only gets you out of the inner switch/select block. If using a “return” statement is not an option then defining a label for the outer loop is the next best thing.
|
|
A “goto” statement will do the trick too…
Iteration Variables and Closures in “for” Statements
- level: intermediate
This is the most common gotcha in Go. The iteration variables in for
statements are reused in each iteration. This means that each closure (aka function literal) created in your for
loop will reference the same variable (and they’ll get that variable’s value at the time those goroutines start executing).
Incorrect:
|
|
The easiest solution (that doesn’t require any changes to the goroutine) is to save the current iteration variable value in a local variable inside the for
loop block.
Works:
|
|
Another solution is to pass the current iteration variable as a parameter to the anonymous goroutine.
Works:
|
|
Here’s a slightly more complicated version of the trap.
Incorrect:
|
|
Works:
|
|
What do you think you’ll see when you run this code (and why)?
|
|
Deferred Function Call Argument Evaluation
- level: intermediate
Arguments for a deferred function call are evaluated when the defer
statement is evaluated (not when the function is actually executing). The same rules apply when you defer a method call. The structure value is also saved along with the explicit method parameters and the closed variables.
|
|
If you have pointer parameters it is possible to change the values they point to because only the pointer is saved when the defer
statement is evaluated.
|
|
Deferred Function Call Execution
- level: intermediate
The deferred calls are executed at the end of the containing function (and in reverse order) and not at the end of the containing code block. It’s an easy mistake to make for new Go developers confusing the deferred code execution rules with the variable scoping rules. It can become a problem if you have a long running function with a for
loop that tries to defer
resource cleanup calls in each iteration.
|
|
One way to solve the problem is by wrapping the code block in a function.
|
|
Another option is to get rid of the defer
statement 🙂
Failed Type Assertions
- level: intermediate
Failed type assertions return the “zero value” for the target type used in the assertion statement. This can lead to unexpected behavior when it’s mixed with variable shadowing.
Incorrect:
|
|
Works:
|
|
Blocked Goroutines and Resource Leaks
- level: intermediate
Rob Pike talked about a number of fundamental concurrency patterns in his “Go Concurrency Patterns” presentation at Google I/O in 2012. Fetching the first result from a number of targets is one of them.
|
|
The function starts a goroutines for each search replica. Each goroutine sends its search result to the result channel. The first value from the result channel is returned.
What about the results from the other goroutines? What about the goroutines themselves?
The result channel in the First()
function is unbuffered. This means that only the first goroutine returns. All other goroutines are stuck trying to send their results. This means if you have more than one replica each call will leak resources.
To avoid the leaks you need to make sure all goroutines exit. One potential solution is to use a buffered result channel big enough to hold all results.
|
|
Another potential solution is to use a select
statement with a default
case and a buffered result channel that can hold one value. The default
case ensures that the goroutines don’t get stuck even when the result channel can’t receive messages.
|
|
You can also use a special cancellation channel to interrupt the workers.
|
|
Why did the presentation contain these bugs? Rob Pike simply didn’t want to comlicate the slides. It makes sense, but it can be a problem for new Go developers who would use the code as is without thinking that it might have problems.
Same Address for Different Zero-sized Variables
- level: intermediate
If you have two different variables shouldn’t they have different addresses? Well, it’s not the case with Go 🙂 If you have zero-sized variables they might share the exact same address in memory.
|
|
The First Use of iota Doesn’t Always Start with Zero
- level: intermediate
It may seem like the iota
identifyer is like an increment operator. You start a new constant declaration and the first time you use iota
you get zero, the second time you use it you get one and so on. It’s not always the case though.
|
|
The iota
is really an index operator for the current line in the constant declaration block, so if the first use of iota
is not the first line in the constant declaration block the initial value will not be zero.
Using Pointer Receiver Methods On Value Instances
- level: advanced
It’s OK to call a pointer receiver method on a value as long as the value is addressable. In other words, you don’t need to have a value receiver version of the method in some cases.
Not every variable is addressable though. Map elements are not addressable. Variables referenced through interfaces are also not addressable.
|
|
Compile Errors:
/tmp/sandbox017696142/main.go:21: cannot use data literal (type data) as type printer in assignment: data does not implement printer (print method has pointer receiver) /tmp/sandbox017696142/main.go:25: cannot call pointer method on m[“x”] /tmp/sandbox017696142/main.go:25: cannot take the address of m[“x”]
Updating Map Value Fields
- level: advanced
If you have a map of struct values you can’t update individual struct fields.
Fails:
|
|
Compile Error:
/tmp/sandbox380452744/main.go:9: cannot assign to m[“x”].name
It doesn’t work because map elements are not addressable.
What can be extra confusing for new Go devs is the fact that slice elements are addressable.
|
|
Note that a while ago it was possible to update map element fields in one of the Go compilers (gccgo), but that behavior was quickly fixed 🙂 It was also considered as a potential feature for Go 1.3. It wasn’t important enough to support at that point in time, so it’s still on the todo list.
The first work around is to use a temporary variable.
|
|
Another workaround is to use a map of pointers.
|
|
By the way, what happens when you run this code?
|
|
“nil” Interfaces and “nil” Interfaces Values
- level: advanced
This is the second most common gotcha in Go because interfaces are not pointers even though they may look like pointers. Interface variables will be “nil” only when their type and value fields are “nil”.
The interface type and value fields are populated based on the type and value of the variable used to create the corresponding interface variable. This can lead to unexpected behavior when you are trying to check if an interface variable equals to “nil”.
|
|
Watch out for this trap when you have a function that returns interfaces.
Incorrect:
|
|
Works:
|
|
Stack and Heap Variables
- level: advanced
You don’t always know if your variable is allocated on the stack or heap. In C++ creating variables using the new
operator always means that you have a heap variable. In Go the compiler decides where the variable will be allocated even if the new()
or make()
functions are used. The compiler picks the location to store the variable based on its size and the result of “escape analysis”. This also means that it’s ok to return references to local variables, which is not ok in other languages like C or C++.
If you need to know where your variables are allocated pass the “-m” gc flag to “go build” or “go run” (e.g., go run -gcflags -m app.go
).
GOMAXPROCS, Concurrency, and Parallelism
- level: advanced
Go 1.4 and below uses only one execution context / OS thread. This means that only one goroutine can execute at any given time. Starting with 1.5 Go sets the number of execution contexts to the number of logical CPU cores returned by runtime.NumCPU()
. That number may or may not match the total number of logical CPU cores on your system depending on the CPU affinity settings of your process. You can adjust this number by changing the GOMAXPROCS
environment variable or by calling the runtime.GOMAXPROCS()
function.
There’s a common misconception that GOMAXPROCS
represents the number of CPUs Go will use to run goroutines. The runtime.GOMAXPROCS()
function documentation adds more to the confusion. The GOMAXPROCS
variable description (https://golang.org/pkg/runtime/) does a better job talking about OS threads.
You can set GOMAXPROCS
to more than the number of your CPUs. As of 1.10 there’s no longer a limit for GOMAXPROCS. The max value for GOMAXPROCS
used to be 256 and it was later increased to 1024 in 1.9.
|
|
Read and Write Operation Reordering
- level: advanced
Go may reorder some operations, but it ensures that the overall behavior in the goroutine where it happens doesn’t change. However, it doesn’t guarantee the order of execution across multiple goroutines.
|
|
If you run this code a few times you might see these a
and b
variable combinations:
1 2
3 4
0 2
0 0
1 4
The most interesting combination for a
and b
is “02”. It shows that b
was updated before a
.
If you need to preserve the order of read and write operations across multiple goroutines you’ll need to use channels or the appropriate constructs from the “sync” package.
Preemptive Scheduling
- level: advanced
It’s possible to have a rogue goroutine that prevents other goroutines from running. It can happen if you have a for
loop that doesn’t allow the scheduler to run.
|
|
The for
loop doesn’t have to be empty. It’ll be a problem as long as it contains code that doesn’t trigger the scheduler execution.
The scheduler will run after GC, “go” statements, blocking channel operations, blocking system calls, and lock operations. It may also run when a non-inlined function is called.
|
|
To find out if the function you call in the for
loop is inlined pass the “-m” gc flag to “go build” or “go run” (e.g., go build -gcflags -m
).
Another option is to invoke the scheduler explicitly. You can do it with the Gosched()
function from the “runtime” package.
|
|
Note that the code above contains a race condition. It was done intentionally to show the sheduling gotcha.
Import C and Multiline Import Blocks
- level: Cgo
You need to import the “C” package to use Cgo. You can do that with a single line import
or you can do it with an import
block.
|
|
If you are using the import
block format you can’t import other packages in the same block.
|
|
Compile Error:
./main.go:13:2: could not determine kind of name for C.free
No blank lines Between Import C and Cgo Comments
- level: Cgo
One of the first gotchas with Cgo is the location of the cgo comments above the import "C"
statement.
|
|
Compile Error:
./main.go:15:2: could not determine kind of name for C.free
Make sure you don’t have any blank lines above import "C"
statement.
Can’t Call C Functions with Variable Arguments
- level: Cgo
You can’t call C functions with variable arguments directly.
|
|
Compile Error:
./main.go:15:2: unexpected type: …
You have to wrap your variadic C functions in functions with a known number of parameters.
|
|
翻译自50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs