检测 Go 程序中按值传递的 locks

mbyd916 · · 7943 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

`go tool vet -copylocks` 命令简介 Go 语言安装包附带 [vet](https://golang.org/cmd/vet/) 命令行工具。该工具能对程序源码运行一套启发式算法以发现可疑的程序结构,如无法执行的代码或对 `fmt.Printf` 函数的错误调用(指 arguments 没有对齐 format 参数): ```go package main import "fmt" func f() { fmt.Printf("%d\n") return fmt.Println("Done") } ``` ``` > go tool vet vet.go vet.go:8: unreachable code vet.go:6: missing argument for Printf("%d"): format reads arg 1, have only 0 args ``` 本文专讲该工具的 copylocks 选项。让我们看看它能做什么以及如何在实际的程序中发挥作用。 假设程序使用互斥锁进行同步: ```go package main import "sync" type T struct { lock sync.Mutex } func (t *T) Lock() { t.lock.Lock() } func (t T) Unlock() { t.lock.Unlock() } func main() { t := T{lock: sync.Mutex{}} t.Lock() t.Unlock() t.Lock() } ``` > 如果变量 v 是可寻址的,并且 &v 的方法集合包含 m,那么 v.m() 是 (&v).m() 的简写。 想一想上述程序运行的结果可能是什么... 程序会进入死锁状态: ``` fatal error: all goroutines are asleep — deadlock! goroutine 1 [semacquire]: sync.runtime_Semacquire(0x4201162ac) /usr/local/go/src/runtime/sema.go:47 +0x30 sync.(*Mutex).Lock(0x4201162a8) /usr/local/go/src/sync/mutex.go:85 +0xd0 main.(*T).Lock(0x4201162a8) ... ``` 运行上述程序得到了糟糕的结果,根本原因是把 receiver 按值传递给 Unlock 方法,所以 `t.lock.Unlock()` 实际上是由 lock 的副本调用的。我们很容易忽视这点,特别在更大型的程序中。Go 编译器不会检测这方面,因为这可能是程序员有意为之。该 vet 工具登场啦... ``` > go tool vet vet.go vet.go:13: Unlock passes lock by value: main.T ``` 选项 copylocks (默认启用) 会检测拥有 Lock 方法 (实际需要 pointer receiver) 的 type 是否按值传递。如果是这种情况,则会发出警告。 sync 包有使用该机制的例子,它有一个命名为 noCopy 的特殊 type。为了避免某 type 按值拷贝 (实际上通过 vet 工具进行检测),需要往 struct 定义中添加一个 field(如 WaitGroup): ```go package main import "sync" type T struct { wg sync.WaitGroup } func fun(T) {} func main() { t := T{sync.WaitGroup{}} fun(t) } ``` ``` > go tool vet lab.go lab.go:9: fun passes lock by value: main.T contains sync.WaitGroup contains sync.noCopy lab.go:13: function call copies lock value: main.T contains sync.WaitGroup contains sync.noCopy ``` 深入理解该机制 ![under-the-hood](https://raw.githubusercontent.com/studygolang/gctt-images/master/Detect-Locks-Passed-by-Value-in-Go/under-the-hood.jpeg) vet 工具的源文件放在 `/src/cmd/vet` 路径下。vet 的每个选项都利用 register 函数进行注册,该函数其中两个参数分别是一个可变参数 (类型是该选项所关注的 AST 结点类型) 和一个回调函数。该回调函数将因特定类型的结点事件触发。对于 copylocks 选项,需要检测的结点包含 return 语句。最终都会转到 lockPath,它验证传递的值是否属于某个 type(拥有一个需要 pointer receiver 的 Lock 方法)。在整个处理过程中,go/ast 包被广泛使用。可以在 Go 源码可测试的示例中找到对该包的简单介绍。 多点击下方的 "????" 按钮, 以帮助其他人找到这篇文章哦。如果您想获得有关新帖子的更新或未来工作进展的消息, 请在这儿或者 Twitter 上关注我。

via: https://medium.com/golangspec/detect-locks-passed-by-value-in-go-efb4ac9a3f2b

作者:Michał Łowicki  译者:mbyd916  校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7943 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传