目录
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}
}
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 前先问两个问题:
- 要不要等全部并发完成? 要等就用
sync.WaitGroup;不用等就只管好共享状态,别让 goroutine 泄漏。 - 有没有共享状态被并发读写? 有就想办法避开(各写各的分片、结果走 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/(.*)//')