其他
我为什么放弃Go语言?
👉腾小云导读
你在什么时候会产生“想要放弃用 Go 语言”的念头?也许是在用 Go 开发过程中,接连不断踩坑的时候。本文作者提炼和总结《100 Go Mistakes and How to Avoid Them》里的精华内容,并结合自身的工作经验,盘点了 Go 的常见典型错误,撰写了这篇超全避坑指南。让我们跟随文章,一起重拾用 Go 的信心~👉目录1 注意 shadow 变量2 慎用 init 函数3 embed types 优缺点4 Functional Options Pattern 传递参数5 小心八进制整数6 float 的精度问题7 slice 相关注意点 slice 相关注意点8 注意 range9 注意 break 作用域10 defer11 string 相关12 interface 类型返回的非 nil 问题13 Error 14 happens before 保证15 Context Values16 应多关注 goroutine 何时停止17 Channel18 string format 带来的 dead lock19 错误使用 sync.WaitGroup20 不要拷贝 sync 类型21 time.After 内存泄露22 HTTP body 忘记 Close 导致的泄露23 Cache line24 关于 False Sharing 造成的性能问题25 内存对齐26 逃逸分析27 byte slice 和 string 的转换优化28 容器中的 GOMAXPROCS29 总结01
if tracing {
client, err := createClientWithTracing()
if err != nil {
return err
}
log.Println(client)
} else {
client, err := createDefaultClient()
if err != nil {
return err
}
log.Println(client)
}
var err error
if tracing {
client, err = createClientWithTracing()
} else {
...
}
if err != nil { // 防止重复代码
return err
}
# command-line-arguments
.\main.go:15:3: declaration of "client" shadows declaration at line 13
.\main.go:21:3: declaration of "client" shadows declaration at line 13
02
使用 init 函数之前需要注意下面几件事:
2.1 init 函数会在全局变量之后被执行
import "fmt"
var a = func() int {
fmt.Println("a")
return 0
}()
func init() {
fmt.Println("init")
}
func main() {
fmt.Println("main")
}
// output
a
init
main
2.2 init 初始化按解析的依赖关系顺序执行
2.3 扰乱单元测试
func init(){
dataSourceName := os.Getenv("MYSQL_DATA_SOURCE_NAME")
d, err := sql.Open("mysql", dataSourceName)
if err != nil {
log.Panic(err)
}
db = d
}
03
Bar
}
type Bar struct {
Baz int
}
writeCloser io.WriteCloser
}
func (l Logger) Write(p []byte) (int, error) {
return l.writeCloser.Write(p)
}
func (l Logger) Close() error {
return l.writeCloser.Close()
}
func main() {
l := Logger{writeCloser: os.Stdout}
_, _ = l.Write([]byte("foo"))
_ = l.Close()
}
io.WriteCloser
}
func main() {
l := Logger{WriteCloser: os.Stdout}
_, _ = l.Write([]byte("foo"))
_ = l.Close()
}
sync.Mutex
m map[string]int
}
func New() *InMem {
return &InMem{m: make(map[string]int)}
}
i.Lock()
v, contains := i.m[key]
i.Unlock()
return v, contains
}
m.Lock() // ??
04
|
port *int
}
type Option func(options *options) error
func WithPort(port int) Option {
// 所有的类型校验,赋值,初始化啥的都可以放到这个闭包里面做
return func(options *options) error {
if port < 0 {
return errors.New("port should be positive")
}
options.port = &port
return nil
}
}
var options options
// 遍历所有的 Option
for _, opt := range opts {
// 执行闭包
err := opt(&options)
if err != nil {
return nil, err
}
}
// 接下来可以填充我们的业务逻辑,比如这里设置默认的port 等等
var port int
if options.port == nil {
port = defaultHTTPPort
} else {
if *options.port == 0 {
port = randomPort()
} else {
port = *options.port
}
}
// ...
}
httplib.WithPort(8080),
httplib.WithTimeout(time.Second))
05
fmt.Println(sum)
06
|
result := 10_000.
for i := 0; i < n; i++ {
result += 1.0001
}
return result
}
func f2(n int) float64 {
result := 0.
for i := 0; i < n; i++ {
result += 1.0001
}
return result + 10_000.
}
b := 1.0001
c := 1.0002
fmt.Println(a * (b + c))
fmt.Println(a*b + a*c)
200030.0020003
b := decimal.NewFromFloat(1.0001)
c := decimal.NewFromFloat(1.0002)
fmt.Println(a.Mul(b.Add(c))) //200030.0020003
07
7.1 区分 slice 的 length 和 capacity
s2 := s1[1:3]
fmt.Println(s1) // [0 2 0]
fmt.Println(s2) // [2 0 3]
fmt.Println(s1) // [0 2 0 4]
fmt.Println(s2) // [2 0 4]
fmt.Println(s1) //[0 2 0 4]
fmt.Println(s2) //[2 0 4 5 6 7]
s2 := s1[1:2]
s3 := append(s2, 10)
7.2 slice 初始化
var s []string
log(1, s)
s = []string(nil)
log(2, s)
s = []string{}
log(3, s)
s = make([]string, 0)
log(4, s)
}
func log(i int, s []string) {
fmt.Printf("%d: empty=%t\tnil=%t\n", i, len(s) == 0, s == nil)
}
2: empty=true nil=true
3: empty=true nil=false
4: empty=true nil=false
var s []string
if foo() {
s = append(s, "foo")
}
if bar() {
s = append(s, "bar")
}
return s
}
dst := append([]int(nil), src...)
BenchmarkConvert_GivenCapacity-4 86 13438544 ns/op
BenchmarkConvert_GivenLength-4 91 12800411 ns/op
7.3 copy slice
var dst []int
copy(dst, src)
fmt.Println(dst) // []
dst := make([]int, len(src))
copy(dst, src)
fmt.Println(dst) //[0 1 2]
dst := append([]int(nil), src...)
7.4 slice capacity内存释放问题
v []byte
}
func keepFirstTwoElementsOnly(foos []Foo) []Foo {
return foos[:2]
}
func main() {
foos := make([]Foo, 1_000)
printAlloc()
for i := 0; i < len(foos); i++ {
foos[i] = Foo{
v: make([]byte, 1024*1024),
}
}
printAlloc()
two := keepFirstTwoElementsOnly(foos)
runtime.GC()
printAlloc()
runtime.KeepAlive(two)
}
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%d KB\n", m.Alloc/1024)
}
1024315 KB
1024319 KB
res := make([]Foo, 2)
copy(res, foos)
return res
}
for i := 2; i < len(foos); i++ {
foos[i].v = nil
}
return foos[:2]
}
08
8.1 copy 的问题
balance float32
}
accounts := []account{
{balance: 100.},
{balance: 200.},
{balance: 300.},
}
for _, a := range accounts {
a.balance += 1000
}
accounts[i].balance += 1000
}
for range s {
s = append(s, 10)
}
8.2 指针问题
ID string
Balance float64
}
test := []Customer{
{ID: "1", Balance: 10},
{ID: "2", Balance: -10},
{ID: "3", Balance: 0},
}
var m map[string]*Customer
for _, customer := range test {
m[customer.ID] = &customer
}
fmt.Printf("%p\n", &customer) //我们想要获取这个指针的时候
}
0x1400000e240
0x1400000e240
current := customer // 使用局部变量
fmt.Printf("%p\n", ¤t) // 这里获取的指针是 range copy 出来元素的指针
}
current := &test[i] // 使用局部变量
fmt.Printf("%p\n", current)
}
09
fmt.Printf("%d ", i)
switch i {
default:
case 2:
break
}
}
for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
switch i {
default:
case 2:
break loop
}
}
select {
case <-ch:
// Do something
case <-ctx.Done():
break
}
}
for {
select {
case <-ch:
// Do something
case <-ctx.Done():
break loop
}
}
10
10.1 注意 defer 的调用时机
for path := range ch {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// Do something with file
}
return nil
}
for path := range ch {
if err := readFile(path); err != nil {
return err
}
}
return nil
}
func readFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// Do something with file
return nil
}
10.2 注意 defer 的参数
i := 0
defer notice(i) // 0
i++
return
}
func notice(i int) {
fmt.Println(i)
}
i := 0
defer notice(&i) // 1
i++
return
}
10.2 defer 下的闭包
i := 0
defer func() {
fmt.Println(i + 1) //12
}()
i++
return i+10
}
func TestA(t *testing.T) {
fmt.Println(a()) //11
}
11
11.1 迭代带来的问题
for i := range s {
fmt.Printf("position %d: %c\n", i, s[i])
}
fmt.Printf("len=%d\n", len(s))
position 1: Ã
position 3: l
position 4: l
position 5: o
len=6
for i, v := range s {
fmt.Printf("position %d: %c\n", i, v)
}
runes := []rune(s)
for i, _ := range runes {
fmt.Printf("position %d: %c\n", i, runes[i])
}
position 1: ê
position 2: l
position 3: l
position 4: o
11.2 截断带来的问题
if len(log) < 36 {
return errors.New("log is not correctly formatted")
}
uuid := log[:36]
s.store(uuid)
// Do something
}
if len(log) < 36 {
return errors.New("log is not correctly formatted")
}
uuid := strings.Clone(log[:36]) // copy一份
s.store(uuid)
// Do something
}
12
errs []string
}
func (m *MultiError) Add(err error) {
m.errs = append(m.errs, err.Error())
}
func (m *MultiError) Error() string {
return strings.Join(m.errs, ";")
}
var m *MultiError
if age < 0 {
m = &MultiError{}
m.Add(errors.New("age is negative"))
}
if name == "" {
if m == nil {
m = &MultiError{}
}
m.Add(errors.New("name is nil"))
}
return m
}
func Test(t *testing.T) {
if err := Validate(10, "a"); err != nil {
t.Errorf("invalid")
}
}
13
13.1 error wrap
if err != nil {
return err
}
if err != nil {
return XXError{Err: err}
}
return fmt.Errorf("xxx failed: %w", err)
}
return fmt.Errorf("xxx failed: %v", err)
}
13.2 error Is & As
func main() {
err1 := fmt.Errorf("wrap base: %w", BaseErr)
err2 := fmt.Errorf("wrap err1: %w", err1)
println(err2 == BaseErr)
if !errors.Is(err2, BaseErr) {
panic("err2 is not BaseErr")
}
println("err2 is BaseErr")
}
err2 is BaseErr
e string
}
func (t TypicalErr) Error() string {
return t.e
}
func main() {
err := TypicalErr{"typical error"}
err1 := fmt.Errorf("wrap err: %w", err)
err2 := fmt.Errorf("wrap err1: %w", err1)
var e TypicalErr
if !errors.As(err2, &e) {
panic("TypicalErr is not on the chain of err2")
}
println("TypicalErr is on the chain of err2")
println(err == e)
}
true
13.3 处理 defer 中的 error
float32, error) {
rows, err := db.Query(query, clientID)
if err != nil {
return 0, err
}
defer rows.Close()
// Use rows
}
err := rows.Close()
if err != nil {
log.Printf("failed to close rows: %v", err)
}
return err //无法通过编译
}()
rows, err = db.Query(query, clientID)
if err != nil {
return 0, err
}
defer func() {
err = rows.Close()
}()
// Use rows
}
closeErr := rows.Close()
if err != nil {
if closeErr != nil {
log.Printf("failed to close rows: %v", err)
}
return
}
err = closeErr
}()
14
创建 goroutine 发生先于 goroutine 执行,所以下面这段代码先读一个变量,然后在 goroutine 中写变量不会发生 data race 问题:
go func() {
i++
}()
go func() {
i++
}()
fmt.Println(i)
var a string
func f() {
a = "hello, world"
c <- 0
}
func main() {
go f()
<-c
print(a)
}
ch := make(chan struct{})
go func() {
<-ch
fmt.Println(i)
}()
i++
close(ch)
var a string
func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
15
type key string
const myCustomKey key = "key"
func f(ctx context.Context) {
ctx = context.WithValue(ctx, myCustomKey, "foo")
// ...
}
16
go func() {
for v := range ch {
// ...
}
}()
newWatcher()
// Run the application
}
type watcher struct { /* Some resources */ }
func newWatcher() {
w := watcher{}
go w.watch()
}
w := newWatcher()
defer w.close()
// Run the application
}
func newWatcher() watcher {
w := watcher{}
go w.watch()
return w
}
func (w watcher) close() {
// Close the resources
}
17
Channel
17.1 select & channel
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
fmt.Println("disconnection, return")
return
}
}
messageCh <- i
}
disconnectCh <- struct{}{}
1
2
3
4
disconnection, return
|
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
for {
select {
case v := <-messageCh:
fmt.Println(v)
default:
fmt.Println("disconnection, return")
return
}
}
}
}
17.2 不要使用 nil channel
ch <- 0 //block
<-ch //block
17.3 Channel 的 close 问题
close(ch1)
for {
v := <-ch1
fmt.Println(v)
}
ch := make(chan int, 1)
go func() {
for {
select {
case v:= <-ch1:
ch <- v
case v:= <-ch2:
ch <- v
}
}
close(ch) // 永远运行不到
}()
return ch
}
fmt.Print(v, open) //open返回false 表示没有被关闭
ch := make(chan int, 1)
ch1Closed := false
ch2Closed := false
go func() {
for {
select {
case v, open := <-ch1:
if !open { // 如果已经关闭
ch1Closed = true //标记为true
break
}
ch <- v
case v, open := <-ch2:
if !open { // 如果已经关闭
ch2Closed = true//标记为true
break
}
ch <- v
}
if ch1Closed && ch2Closed {//都关闭了
close(ch)//关闭ch
return
}
}
}()
return ch
}
18
mutex sync.RWMutex
id string
age int
}
func (c *Customer) UpdateAge(age int) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if age < 0 {
return fmt.Errorf("age should be positive for customer %v", c)
}
c.age = age
return nil
}
func (c *Customer) String() string {
fmt.Println("enter string method")
c.mutex.RLock()
defer c.mutex.RUnlock()
return fmt.Sprintf("id %s, age %d", c.id, c.age)
}
19
var v uint64
for i := 0; i < 3; i++ {
go func() {
wg.Add(1)
atomic.AddUint64(&v, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println(v)
20
mu sync.Mutex
counters map[string]int
}
func (c Counter) Increment(name string) {
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}
func NewCounter() Counter {
return Counter{counters: map[string]int{}}
}
func main() {
counter := NewCounter()
go counter.Increment("aa")
go counter.Increment("bb")
}
|
# github.com/cch123/gogctuner/main
./main.go:53:9: Increment passes lock by value: github.com/cch123/gogctuner/main.Counter contains sync.Mutex
21
import (
"fmt"
"time"
)
//define a channel
var chs chan int
func Get() {
for {
select {
case v := <- chs:
fmt.Printf("print:%v\n", v)
case <- time.After(3 * time.Minute):
fmt.Printf("time.After:%v", time.Now().Unix())
}
}
}
func Put() {
var i = 0
for {
i++
chs <- i
}
}
func main() {
chs = make(chan int, 100)
go Put()
Get()
}
delay := time.NewTimer(3 * time.Minute)
defer delay.Stop()
for {
delay.Reset(3 * time.Minute)
select {
case v := <- chs:
fmt.Printf("print:%v\n", v)
case <- delay.C:
fmt.Printf("time.After:%v", time.Now().Unix())
}
}
}
22
client http.Client
url string
}
func (h handler) getBody() (string, error) {
resp, err := h.client.Get(h.url)
if err != nil {
return "", err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
err := resp.Body.Close()
if err != nil {
log.Printf("failed to close response: %v\n", err)
}
}()
23
64
var total int64
for i := 0; i < len(s); i += 2 {
total += s[i]
}
return total
}
func sum8(s []int64) int64 {
var total int64
for i := 0; i < len(s); i += 8 {
total += s[i]
}
return total
}
a int64
b int64
}
func sumFoo(foos []Foo) int64 {
var total int64
for i := 0; i < len(foos); i++ {
total += foos[i].a
}
return total
}
a []int64
b []int64
}
func sumBar(bar Bar) int64 {
var total int64
for i := 0; i < len(bar.a); i++ {
total += bar.a[i]
}
return total
}
s := Bar{
a: make([]int64, 16),
b: make([]int64, 16),
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
sumBar(s)
}
})
}
func Benchmark_sumFoo(b *testing.B) {
s := make([]Foo, 16)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
sumFoo(s)
}
})
}
Benchmark_sumBar-16 249029368 4.855 ns/op
Benchmark_sumFoo-16 238571205 5.056 ns/op
24
IncreaseAllEles()
}
type Pad struct {
a uint64
_p1 [15]uint64
b uint64
_p2 [15]uint64
c uint64
_p3 [15]uint64
}
func (myatomic *Pad) IncreaseAllEles() {
atomic.AddUint64(&myatomic.a, 1)
atomic.AddUint64(&myatomic.b, 1)
atomic.AddUint64(&myatomic.c, 1)
}
type NoPad struct {
a uint64
b uint64
c uint64
}
func (myatomic *NoPad) IncreaseAllEles() {
atomic.AddUint64(&myatomic.a, 1)
atomic.AddUint64(&myatomic.b, 1)
atomic.AddUint64(&myatomic.c, 1)
}
paraNum := 1000
addTimes := 1000
var wg sync.WaitGroup
wg.Add(paraNum)
for i := 0; i < paraNum; i++ {
go func() {
for j := 0; j < addTimes; j++ {
myatomic.IncreaseAllEles()
}
wg.Done()
}()
}
wg.Wait()
}
func BenchmarkNoPad(b *testing.B) {
myatomic := &NoPad{}
b.ResetTimer()
testAtomicIncrease(myatomic)
}
func BenchmarkPad(b *testing.B) {
myatomic := &Pad{}
b.ResetTimer()
testAtomicIncrease(myatomic)
}
BenchmarkNoPad-10 1000000000 0.1360 ns/op
BenchmarkPad
BenchmarkPad-10 1000000000 0.08887 ns/op
25
m int64
x struct{}
}
type N struct {
x struct{}
n int64
}
func main() {
m := M{}
n := N{}
fmt.Printf("as final field size:%d\nnot as final field size:%d\n", unsafe.Sizeof(m), unsafe.Sizeof(n))
}
not as final field size:8
$ fieldalignment -fix .\main\my.go
main\my.go:13:9: struct of size 24 could be 16
26
|
26.1 指针逃逸
name string
}
func createDemo(name string) *Demo {
d := new(Demo) // 局部变量 d 逃逸到堆
d.name = name
return d
}
func main() {
demo := createDemo("demo")
fmt.Println(demo)
}
# command-line-arguments
main\main.go:12:17: leaking param: name
main\main.go:13:10: new(Demo) escapes to heap
main\main.go:20:13: ... argument does not escape
&{demo}
26.2 interface{}/any 动态类型逃逸
d := new(Demo) // 局部变量 d 逃逸到堆
d.name = name
return d
}
26.3 切片长度或容量没指定逃逸
number := 10
s1 := make([]int, 0, number)
for i := 0; i < number; i++ {
s1 = append(s1, i)
}
s2 := make([]int, 0, 10)
for i := 0; i < 10; i++ {
s2 = append(s2, i)
}
}
./main.go:65:12: make([]int, 0, number) escapes to heap
./main.go:69:12: make([]int, 0, 10) does not escape
26.4 闭包
n := 0
return func() int {
n++
return n
}
}
func main() {
in := Increase()
fmt.Println(in()) // 1
fmt.Println(in()) // 2
}
./main.go:64:5: moved to heap: n
./main.go:65:12: func literal escapes to heap
27
func toBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
// toString performs unholy acts to avoid allocations
func toString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
28
func main() {
// Your application logic here
}
29
参考:
https://go.dev/ref/mem
https://colobu.com/2019/01/24/cacheline-affects-performance-in-go/
https://teivah.medium.com/go-and-cpu-caches-af5d32cc5592
https://geektutu.com/post/hpg-escape-analysis.html
https://github.com/uber-go/automaxprocs
https://gfw.go101.org/article/unsafe.html
使用Go语言时还有什么易错点?欢迎在评论区分享。我们将选取1则最有意义的分享,送出腾讯云开发者-文化衫1件(见下图)。6月12日中午12点开奖。