首先,为啥需要nocopy?

对 mutex 的copy是不安全的,即使一个mutex释放了这把锁,那个被copy的mutex还是没有释放,这是不安全的。

那么如何做到?今天去搜这个东西,搜到一篇 blog,感觉讲的可以,所以翻译一下。

原文:https://bronzesword.medium.com/what-does-nocopy-after-first-use-mean-in-golang-and-how-12396c31de47

作者:jing

翻译:Google翻译

校对:我

当我们阅读 golang 源码或学习使用一些内置 struct 时,经常会被告知“首次使用后不能复制”,例如 sync.Cond、sync.Map、sync.Mutex(几乎 sync 包中所有类型都有 ) 和strings.Builder。出于安全原因,大多数情况下这是必需的。

例如,您有一个带有指针字段的 struct 并且您不希望它被复制,因为浅拷贝会使这两个对象(struct 的实例)持有相同指针,这是不安全的。那么 golang 做了什么来确保这一点呢?据我所知,没有一个完美的答案(请参阅此处的讨论)。但在这个blog中,我将回顾以下两种解决方案,并希望它能提供更好的理解方式。

运行时检查

这是通过封装一个指向自身的指针并在任何进一步操作之前检查来完成的。一个典型的例子是 strings.Builder :

type Builder struct {addr *Builderbuf []byte
}
func (b *Builder) copyCheck() {if b.addr == nil {b.addr = (*Builder)(noescape(unsafe.Pointer(b)))} else if b.addr != b {panic("strings: illegal use of non-zero Builder copied by value")}
}
func (b *Builder) Write(p []byte) (int, error) {b.copyCheck()...
}
// test case
var a strings.Builder
a.Write([]byte("testa"))
var b = a
b.Write([]byte("testb"))   // will panic here

如您所见,当我们声明builder a并写入时,a.addr将被分配变量 a 的地址。我们把a赋值给b之后,a.addr会被浅拷贝到b.addr,但是b新分配的地址肯定和a.addr不一样,所以panic就发生了。该解决方案利用了指针被浅拷贝的事实且易于理解。

另一个简单的例子是 sync.Cond

type Cond struct {noCopy  noCopyL       Lockernotify  notifyListchecker copyChecker
}
type copyChecker uintptr
func (c *copyChecker) check() {if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&uintptr(*c) != uintptr(unsafe.Pointer(c)) {panic("sync.Cond is copied")}
}
func (c *Cond) Wait() {c.checker.check()...
}

乍一看 check() 函数有点难以理解,所以让我们定义一个类似的结构并尝试找出原因:

type cond struct {checker copyChecker
}
type copyChecker uintptr
func (c *copyChecker) check() {fmt.Printf("Before: c: %v, *c: %v, uintptr(*c): %v, uintptr(unsafe.Pointer(c)): %v\n", c, *c, uintptr(*c), uintptr(unsafe.Pointer(c)))atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c)))fmt.Printf("After: c: %v, *c: %v, uintptr(*c): %v, uintptr(unsafe.Pointer(c)): %v\n", c, *c, uintptr(*c), uintptr(unsafe.Pointer(c)))
}
// test case
var a cond
a.checker.check()
b := a
b.checker.check()
// results
Before: c: 0x414020, *c: 0, uintptr(*c): 0, uintptr(unsafe.Pointer(c)): 4276256
After: c: 0x414020, *c: 4276256, uintptr(*c): 4276256, uintptr(unsafe.Pointer(c)): 4276256
Before: c: 0x414044, *c: 4276256, uintptr(*c): 4276256, uintptr(unsafe.Pointer(c)): 4276292
After: c: 0x414044, *c: 4276256, uintptr(*c): 4276256, uintptr(unsafe.Pointer(c)): 4276292

很明显,当我们声明 a 时,它的 checker 字段是 0 并且 checker 字段的地址是 0x414020 或十进制的 4276256 。在 CompareAndSwapUintptr() 之后,它的 checker 字段被分配了自己的地址,比如 4276256 。当我们将 a 赋值给 b 时,a 的 checker 域被复制到 b 的域,但是 b 的 checker 域的地址实际上是 0x414044 或十进制的 4276292。所以它最终满足了所有三个条件(实际上是两个)并检测到复制。这仍然是一种“自指针”方法,我想你明白了。 总而言之,运行时检查通常使用自指针,直到运行时才会检查。

总而言之,运行时检查通常使用自指针,直到运行时才会检查。

go vet copylocks 检查

-copylocks 实际上是一个 go vet 标志,用于检查 locker 类型是否被复制。locker 类型是具有 Lock() 和 Unlock() 方法的类型。(有关更多详细信息,请参见此处)。如前所述,几乎所有同步包中的类型都不能被复制,实际上它只是通过封装一个 noCopy 结构来保证的:

// src/sync/cond.go
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
// sync.Pool
type Pool struct {noCopy noCopy  ...
}
// sync.WaitGroup
type WaitGroup struct {noCopy noCopy  ...
}

Go vet 将检查一个 locker 的每个语句和操作,这样可以在运行前找到copy操作。因此,如果您希望不复制类型,您只需在包中定义一个 noCopy 结构并将其封装为额外字段,如下所示:

type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type MyType struct {noCopy noCopy...
}

go vet 就可以保证没有 copy 了

结论 尽管目前我们对这个问题还没有一个完美的答案,但这两种方法在类似的情况下仍然具有指导意义。自我指针或去使用go vet?下次需要的时候可以试试看 :)

A Mutex must not be copied after first use. 是什么(nocopy)相关推荐

  1. golang goroutine实现_golang中的Mutex设计原理详解(一)

    Mutex系列是根据我对晁岳攀老师的<Go 并发编程实战课>的吸收和理解整理而成,如有偏差,欢迎指正~ 目标 本系列除了希望彻底学习和了解 golang 中 sync.Mutex 的原理和 ...

  2. go sync.Mutex

    并发编程中,不免涉及到共享资源的操作,go也提供简易的互斥锁作为访问控制的手段sync.Mutex,通过简单的Lock()进行加锁,Unlock()进行释放. 接下来我们从源码入手,看看加锁和解锁到底 ...

  3. Golang 基础:底层并发原语 Mutex RWMutex Cond WaitGroup Once等使用和基本实现

    文章目录 互斥锁 Mutex 拷贝使用 Mutex 的问题 读写锁 RWMutex 条件变量 Cond 等待组 WaitGroup 仅执行一次 Once 原子操作 其他 上一篇 <原生并发 go ...

  4. Go 1.19.3 sync.Mutex原理简析

    互斥锁 互斥,数学名词,事件A与事件B在任何一次试验中都不会同时发生,则称事件A与事件B互斥.(来自百度百科) 互斥锁的出现时为了保护共享资源,同一时刻只有锁的持有者才能操作该资源. Go Mutex ...

  5. go学习笔记 sync/mutex源码

    Mutex 是一个互斥锁,可以创建为其他结构体的字段:零值为解锁状态.Mutex 类型的锁和线程无关,可以由不同的线程加锁和解锁. 在一个goroutine获得 Mutex 后,其他goroutine ...

  6. go标准库的学习-sync互斥

    https://studygolang.com/pkgdoc 导入方法: import "sync" sync包提供了基本的同步基元,如互斥锁.除了Once和WaitGroup类型 ...

  7. Go-Mutex互斥量

    先来看一段go1.12.5中Mutex的源码: // Copyright 2009 The Go Authors. All rights reserved. // Use of this source ...

  8. 从项目的一个 panic 说起:Go 中 Sync 包的分析应用

    项目开发中遇到一个错误 "fatal error: concurrent map read and map write". 有过一两年 Golang 开发经验的同学应该都不陌生,这 ...

  9. Golang: 让你的零值更有用

    Make the zero value useful. –Go Proverbs 让我们从 Golang blog 开始吧: The zero value 当内存被分配来存储一个值时,无论是通过声明还 ...

最新文章

  1. oracle plsql开启并行,Oracle开启并行的几种方法
  2. PNAS:微生物组互作塑造宿主适应度
  3. R语言ggplot2可视化:通过在element_text函数中设置ifelse判断条件自定义标签文本的显示格式:例如、粗体、斜体等
  4. Python自动化之YAML解析
  5. 如何让squid实现动态缓存
  6. leetcode1721. 交换链表中的节点
  7. c++矩阵类_面向对象有限元编程|单元类
  8. jQuery 教程01——jQuery安装
  9. SSIS包如何动态指定文件路径
  10. cacti监控java,Cacti监控tomcat的方法
  11. 传奇霸业维护服务器,37传奇霸业6月21日部分区服维护计划
  12. 注入学习(3) Mysql+php注入 基于bool和时间的盲注
  13. ubuntu mysql 5.7 出错_ubuntu mysql5.7 启动提示错误:/var/run/mysqld/mysqld.sock
  14. javascript测试题和参考答案
  15. DataFormatString格式
  16. 使用wps把word格式文件转换成pdf文件
  17. mac u盘重装系统(monterey)
  18. 奇趣分享综合趣事百科文章类型discuz模板
  19. IMDB电影排行爬取分析
  20. Hadoop大数据解决方案

热门文章

  1. dlib重新训练dlib_face_recognition_resnet_model_v1.dat
  2. 工作描述的介绍|如何写工作描述
  3. 工作这些年 (zz)
  4. 华科计算机学院专业课,华科计算机考研专业课有哪些
  5. 面试和谈薪技巧及如何避开常见的陷阱
  6. android脚本实现自动捉妖,一起来捉妖自动捉妖脚本使用教程ios00
  7. 静下心来,无欲则刚——源自内心的骄傲
  8. STM32学习笔记1(初识STM32)
  9. 2020年中国工程机械租赁行业现状及市场竞争格局分析,工程机械运营市场集中度极低,高空作业平台市场集中度较高「图」
  10. 2023养老展|山东养老用品展|老年护理产品展|医养健康展