问题引入

学习golang(v1.16)的 WaitGroup 代码时,看到了一处奇怪的用法,见下方类型定义:

    type WaitGroup struct {
noCopy noCopy
...
}

这里,有个奇怪的“noCopy”类型,顾名思义,这个应该是某种“不可复制”的意思。下边是noCopy类型的定义:

    // noCopy may be embedded into structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
// 对应github链接:https://github.com/golang/go/issues/8005#issuecomment-190753527
type noCopy struct {}
// Lock is a no-op used by -copylocks checker from `go vet`
func (*noCopy) Lock{}
func (*noCopy) Unlock{}
// 以上 Lock 和 Unlock 方法属于 Locker 接口类型的方法集,见 sync/mutex.go

这里有2点比较特别:

  1. noCopy 类型是空 struct
  2. noCopy 类型实现了两个方法: Lock 和 Unlock,而且都是空方法(no-op)。注释中有说,这俩方法是给 go vet 的 copylocks 检测器用的

也就是说,这个 noCopy 类型和它的方法集,没有任何实质的功能属性。那么它是用来做什么的呢?

动手试试

从类型定义,以及实现的lock方法的注释可以看出,noCopy 是为了实现对不可复制类型的限制。这个限制如何起作用呢?参考注释中给出的 issuecomment 链接,在Russ Cox 的评论中,看的这么一句:

A package can define:

type noCopy struct{}
func (*noCopy) Lock() {}

and then put a noCopy noCopy into any struct that must be flagged by vet.

原来这个noCopy的用处,是为了让被嵌入的container类型,在用go vet工具进行copylock check时,能被检测到。

我写了一段代码试了下:

    // file: main.go
package main
import "fmt"
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type cool struct {
Val int32
noCopy
} func main() {
c1 := cool{Val:10,}
c2 := c1 // <- 赋值拷贝
c2.Val = 20
fmt.Println(c1, c2) // <- 传参拷贝
}

然后,我先用vet工具检查了一下:

    leo@leo-MBP % go vet main.go
# command-line-arguments
./main.go:14:8: assignment copies lock value to c2: command-line-arguments.cool
./main.go:16:14: call of fmt.Println copies lock value: command-line-arguments.cool
./main.go:16:18: call of fmt.Println copies lock value: command-line-arguments.cool

上边的输出可以看到,在代码标记出来的两处位置,vet打印了“copy lock value”的提示。

查找资料

试着查了一下这个提示的相关信息,发现这一篇博文:Detect locks passed by value in Go

同时,用go tool vet help copylocks命令可以查看 vet 对 copylocs 分析器的介绍:

copylocks: check for locks erroneously passed by value

Inadvertently copying a value containing a lock, such as sync.Mutex or

sync.WaitGroup, may cause both copies to malfunction. Generally such

values should be referred to through a pointer.

原来,vet 工具的 copylocks 检测器有这么一个功能:检测带锁类型(如 sync.Mutex) 的错误复制使用,这种不当的复制,会引发死锁。

其实不仅仅是sync.Mutex类型会这样,所有需要用到Lock和Unlock方法的类型,即 lock type,都有这种 “错误复制引发死锁” 的隐患。

所以,我们在上边测试的代码中定义的noCopy类型,实现了LockUnlock方法,使得 noCopy 成了一个 lock type,目的就是为了能利用 vet 的 copylocks 分析器对 copy value 的检测能力。

岔个题

虽然上边的测试代码,在用 go vet 检测时给出了提示信息,但是这并不是警告,相应代码没有语法错误,仍然是可执行的,run 一下试试:

leo@leo-MBP % go run main.go
{10 {}} {20 {}}

嵌入了 noCopy 类型的 cool 类型,在被强行复制之后,依然可以运行。noCopy 这种设计的意义,在于防范不当的 copylocks 发生,且这种防范不是强制的,依靠开发者自行检测。

空 struct

好,明白了 noCopy 的存在的意义,接下来探究一下 noCopy 为什么要设计成空 struct 类型。

先上结论:使用空 struct 是出于性能考虑。

    package main

    import (
"fmt"
"unsafe"
) type cool struct{} func main() {
c := cool{}
fmt.Println(unsafe.Sizeof(c)) // -> print 0
}

如上所示,空 struct 类型的值不占用内存空间,所以在性能上更有优势。

总结

综合来看,noCopy 空 struct 类型,结合了 vet 工具对 copylocks 检测的支持,以及空 struct 对性能的优化,用在 “标记不可复制类型” 的场景下,是比较巧妙的设计。

参考

Detect locks passed by value in Go

The empty struct

Go 空结构体 struct{} 的使用

golang sync.noCopy 类型 —— 初探 copylocks 与 empty struct的更多相关文章

  1. Golang Sync.WaitGroup 使用及原理

    Golang Sync.WaitGroup 使用及原理 使用 func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.A ...

  2. golang 函数作为类型

    golang 函数作为类型 package main import "fmt" type A func(int, int) func (f A)Serve() { fmt.Prin ...

  3. 学习Golang语言(6):类型--切片

    学习Golang语言(1): Hello World 学习Golang语言(2): 变量 学习Golang语言(3):类型--布尔型和数值类型 学习Golang语言(4):类型--字符串 学习Gola ...

  4. golang md5 结果类型

    golang  md5 结果类型 package main import ( "crypto/md5" "encoding/hex" "fmt&quo ...

  5. Golang 的 `[]interface{}` 类型

    Golang 的 []interface{} 类型 我其实不太喜欢使用 Go 语言的 interface{} 类型,一般情况下我宁愿多写几个函数:XxxInt, XxxFloat, XxxString ...

  6. Golang:sync.Map

    由于map在gorountine 上不是安全的,所以在大量并发读写的时候,会出现错误. 在1.9版的时候golang推出了sync.Map. sync.Map 通过阅读源码我们发现sync.Map是通 ...

  7. 把《c++ primer》读薄(3-1 标准库string类型初探)

    督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 问题1:养成一个好习惯,在头文件中只定义确实需要的东西 using namespace std; //建议需要什么再using声 ...

  8. golang sync/atomic

    刚刚学习golang原子操作处理的时候发现github上面一个比较不错的golang学习项目 附上链接:https://github.com/polaris1119/The-Golang-Standa ...

  9. golang sync包

    sync 在golang 文档上,golang不希望通过共享内存来进行进程间的协同操作,而是通过channel的方式来进行,当然,golang也提供了共享内存,锁等机制进行协同操作的包: 互斥锁: M ...

随机推荐

  1. Python3中的List列表的相关操作

    列表对象内建函数 1. append(obj) 在列表的末尾添加新元素obj.例: >>> a = ['a', 'b', 'c'] >>> a.append('d' ...

  2. DDD领域驱动理解

    在理解领域驱动的时候,网上很多大谈理论的文章,这种对于初学者不是太容易接受.根据我自己的学习经历,建议按照如下几个步骤学习: 粗略的看一遍领域驱动的理论,做到心中有形,知道领域驱动是什么,解决什么问题 ...

  3. 进程之间的通信(multiprocess.Queue)

    一.进程间通信 进程之间的数据是相互隔离的,例如 from multiprocessing import Process def task(): global n # 声明全局变量 n = 999 p ...

  4. 如果被问到 HTTP 协议,你真的能讲清楚吗?

    前段时间,在和许久未见的老同学聊天时,突然被问到 http 协议到底是什么?脑海里面第一时间想起来的就是 request 请求.response 响应之类的词汇,但是这样讲他真的能知道是什么吗?我反问 ...

  5. 关于Java集合框架的总结

    Java集合框架(都实现了Cloneable和Serializable接口)支持以下两个类型的容器: 一种是为了存储一个元素集合,简称集合(collection). 另一种是为了存储键/值对,称为图( ...

  6. nacos在nginx下集群以及数据库问题

    持久化mysql时指定数据库编辑application.properties spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql ...

  7. 30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)

    前言 本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性.框架的原理以问题的形式进行整理汇总,意在帮助作者及读者自测下 Vue 掌握的程度.本文章节结构以从易到难进行组织,建议读者按章节顺序 ...

  8. 前端axios请求二进制数据流转换生成PDF文件空白问题(终极解决方案)

    本文章共1570字,预计阅读时间1 - 3分钟. 问题场景: axios请求二进制数据转换生成PDF空白问题,使用axios请求后端接口,后端返回的二进制流文件,需要转换成PDF,但是在postman ...

  9. Mysql 5.6 编译报错

    编译安装 mysql(下面是编译参数) /usr/local/bin/cmake . -DCMAKE_INSTALL_PREFIX=/application/mysql-5.6.36 \ -DMYSQ ...

  10. linux centos7 控制台下的一些操作

    2021-08-20一. 快捷键1. Ctrl+d       退出当前用户2. Ctrl+Insert  复制3. Shift+Insert 粘贴4. Ctrl+l       清屏二. 基础命令1 ...