Go

状态:参考

最后更新:

目录

effective_go

redeclare is ok

such as

f, err := os.Open(name)
...
d, err := f.Stat()

above such code, err used twice.

for

go use for instead of while and do while

// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

//If you're looping over an array, slice, string, or map, or reading from a channel, a range clause can manage the loop.
for key, value := range oldMap {
    newMap[key] = value
}

//If you only need the first item in the range (the key or index), drop the second:
for key := range m {
    if key.expired() {
        delete(m, key)
    }
}
//If you only need the second item in the range (the value), use the blank identifier, an underscore, to discard the first:
sum := 0
for _, value := range array {
    sum += value
}

defer

Defer

Deferred functions are executed in LIFO order when return

defer 后面必须是函数调用(带括号),否则编译不过。

defer doSomething()

data

make creates slices, maps, and channels only

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
}

can be instead by

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    return &File{fd, name, nil, 0}
}

print

printf

func Printf(format string, v ...any) (n int, err error) {}
// v is any number of any type
// Go 1.18 起 any 是 interface{} 的别名,二者完全等价,惯例写 any
// btw, below is the usage of ...
func Min(a ...int) int {
    min := int(^uint(0) >> 1)  // largest int
    for _, i := range a {
        if i < min {
            min = i
        }
    }
    return min
}

方法接收者

要改内部状态就得用指针接收者,值接收者拿到的是副本:

func (t Task) SetA(v int) { t.a = v }  // 改的是副本,调用方看不到
func (t *Task) SetB(v int) { t.b = v } // 改的是原对象

两点容易混:

  • 可寻址的变量可以直接调指针方法t.SetB(1) 能编译是因为编译器自动取了 (&t).SetB(1)。所以「指针方法只能用指针调」是错的,限制只出现在不可寻址的值上(比如 map 元素、函数返回值)。
  • 方法集不一样*T 的方法集包含值接收者和指针接收者的方法,T 只包含值接收者的方法。所以接口里有指针接收者的方法时,只有 *T 满足它,T 不满足。

实践上同一个类型的接收者保持统一,不要一半值一半指针。

interface

Go type 不需要显式声明它实现了接口,仅通过实现接口来实现接口

// embedding 
type Job struct {
    Command string
    *log.Logger // 嵌入字段,Logger 的方法被提升到 Job 上(method promotion)
}

func (job *Job) Printf(format string, args ...interface{}) {
    job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}

concurrency

Do not communicate by sharing memory; instead, share memory by communicating.

add go before func to make it run concurrently

func Announce(message string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()  // Note the parentheses - must call the function.
}

on above, go func is closures, how to sent signal? Use channels

channels

channels like the slide with semaphore.

// init
// if no buffer, <-ci wait until something -> ci
ci := make(chan int)   //no buffer
cj := make(chan int,10)//buffer

go func() 内存共享

版本分界:Go 1.22 之前,for 的循环变量在整个循环中只有一份,闭包会捕获同一个变量,需要 solve1/solve2 那样显式传参或 req := req;Go 1.22 及之后循环变量每次迭代都是新变量,下面第一段代码不再有 bug,req := req 也不再必要。

// Go 1.22 之前:不能确保闭包里的 req 是本次迭代的那个
func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func() {
            process(req) // Buggy before Go 1.22; see explanation below.
            <-sem
        }()
    }
}

//solve1: args sent to func
func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func(req *Request) {
            process(req)
            <-sem
        }(req)
    }
}
//solve2: redeclare
func Serve(queue chan *Request) {
    for req := range queue {
        req := req // Create new instance of req for the goroutine.
        sem <- 1
        go func() {
            process(req)
            <-sem
        }()
    }
}

server by channels

func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *Request, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // Wait to be told to exit.
}

go concurrency

动手写 goroutine 前先问两个问题:

  1. 要不要等全部并发完成? 要等就用 sync.WaitGroup;不用等就只管好共享状态,别让 goroutine 泄漏。
  2. 有没有共享状态被并发读写? 有就想办法避开(各写各的分片、结果走 channel 汇总);避不开就上锁。

涉及 data race 一律加锁。需要唤醒等待方时才用 sync.Cond,只是传值就用 channel。

ch := make(chan bool)     // chan 是关键字,不能用作变量名
var wg sync.WaitGroup
var mu sync.Mutex         
cond := sync.NewCond(&mu) // 需要用到 broadcast 的地方才需要 cond,不然直接 chan

go channels: synchronous communication mechanism 如果是 unbuffer channels,那么无论是只有发送方还是只有接收方,都会造成阻塞,因为 channels 的本质是同步(buffer channel 也只会延迟这个过程)。

goroutine & mu

# no race
func Lock_() {
	task := Task{
		mu:    sync.Mutex{},
		peers: nil,
	}
	for i := 0; i < 10000; i++ {
		task.peers = append(task.peers, i+i)
	}
	var wg sync.WaitGroup
	wg.Add(len(task.peers))
	task.mu.Lock()
	for i, i2 := range task.peers {
		go func(i int, i2 int) {
			defer wg.Done()
			task.mu.Lock()
			fmt.Printf("%v %v\n", i, i2)
			task.mu.Unlock()
		}(i, i2)
	}
	task.mu.Unlock()
	wg.Wait()
}

go gc

如果数组需要改动,将不需要的部分设为 nil,可以让 gc 识别到并回收。

go pprof

go test -run TestSpeed3A -memprofile mem.prof
go tool pprof -http=:8081 mem.prof
dstest -v2 -s -p10 -n10 -o log -r $(grep -oE '^func Test[A-Za-z0-9_]+' test_test.go | sed 's/^func //; s/(.*)//')