Golang 随笔
1 goroutine
1.1 论同步的重要性
详见官网
Goroutine对于内存的操作顺序需要同步。
对于单一goroutine,对于内存的读写是和程序代码顺序一致的。例如:代码中先写varA
,再写varB
,那么总是能保证varA
先于varB
被改变。
然而,对于多个goroutine,它们对于同一片内存的访问并不一定是按照代码的顺序进行的。不同goroutine对同一个资源的访问,如果不进行同步,往往会导致错误的结果。
Go的文档里有说:
A read r may observe the value written by a write w that happens concurrently with r. Even if this occurs, it does not imply that reads happening after r will observe writes that happened before w.
也就是说,可能会有以下的情况发生:
+----------+ +-----------+
| gr1 | | gr2 |
+-----+----+ +-----+-----+
| |
|write varA |
| |
|write varB |
| |
| | read varB (changed)
| |
| | read varA (NOT CHANGED!!!)
| |
这么一来,类似下面的代码也就不正确了:
main()
中不能保证总是输出hello, world
.
解决的办法是使用同步原语,包括 chan
和 sync
中的锁。
1.2 使用select的时候需要考虑竞争条件
详见官方博客
考虑有一个处理程序,它的职责是处理从各个goroutine输入的请求中的第一个,丢掉其他的请求(例如:从多个数据库读某个记录,仅处理第一个返回的结果)。程序一般会写成像下面这样的模式:
注意,这里新执行的goroutine都是非block形式的,也即如果当前channel满了或者没有receiver,则执行到default
分支。
上面的代码有一个潜在的问题:如果主函数中的return <-ch
执行前,所有goroutine都已经结束了的话,那么主函数会一直卡在那里,从而报运行时错误。
一个类似的必现例子如下:
解决方法:不使用no buffer的channel,而是将channel的buffer数设置为需要接收的请求个数,这样甚至连default分支都不需要了。
2 接口
2.1 接口的receiver
接口可以被任何非接口的类型实现,包括结构体类型,基本类型(e.g. type T int
),甚至函数类型(e.g. type T func()
).
例如:
注意,上面接口实现中传递的是指针。在Go语言中有这么一条规则:
如果接口receiver声明的是指针类型,那么它只能被指针类型调用;如果接口receiver声明的是值类型,那么它可以被值类型调用,也可以被指针类型调用。
这条规则的后面部分能工作的原因是因为Go会在需要用到值的地方,自动将指针解引用获得对应的值,再继续(调用)。
这条规则的前面部分不接受以值类型调用,是因为以指针作为receiver的method可以修改指针指向的对象,如果传入的是个值(值传递会创建一个临时变量),那么如果Go自动获得这个值的地址,并对其做修改,这些修改只会影响这个临时变量,而不是method的调用者(如果传入的是类似于C++的引用,说不定就没这个限制了)。Go为了避免这种错误,所以做了这么一个限制。
但是,这条规则后面又针对指针类型的receiver提到:
当以值类型去调用指针类型的接口,如果值是可寻址的,那么Go会自动在获得它的地址之后再去调用。
(详见:这里)
2.2 接口定义
一般定义一个接口的时候,开发者会在包中定义一个receiver(例如,结构体),然后通常会提供一个NewXXX
函数来返回这个receiver的指针,供用户调用(例如上面的NewDog()
)。
3 Debug
3.1 Goland 调试多个go routine
假设有以下代码:
package main
import (
"fmt"
"strings"
)
func main() {
sendCh := make(chan string)
recvCh := make(chan string)
launchRoutine(recvCh, sendCh) // A
sendCh <- "hello, world!"
fmt.Println(<-recvCh)
}
func launchRoutine(sendCh, recvCh chan string) {
go func() {
for {
s := strings.ToUpper(<-recvCh)
sendCh <- s // B
}
}()
}
在Goland里同时在A和B点设置断点,然后开始运行。程序首先停止在A点,如果这时候使用Step Over
,那debugger仅会在当前的goroutine中继续执行,而忽略其他goroutine中的断点。如果你想同时在其他goroutine中也能够停止,那么使用Run
或者Run to Cursor
.
Comments