编程 Golang 中你应该知道的 noCopy 策略

2024-11-19 05:40:53 +0800 CST views 933

Golang 中你应该知道的 noCopy 策略

1.1 sync.noCopy 类型

在学习 WaitGroup 代码时,我注意到了 noCopy 类型,并且看到一个很熟悉的注释:"must not be copied after first use"。

// A WaitGroup must not be copied after first use.
//
// In the terminology of the Go memory model, a call to Done
// “synchronizes before” the return of any Wait call that it unblocks.
type WaitGroup struct {
    noCopy noCopy

    state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
    sema  uint32
}

通过搜索,可以发现“must not be copied after first use”和 noCopy 类型经常同时出现。

// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}

// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

观察 noCopy 的定义,我们可以得出以下几点:

  1. noCopy 类型是空的结构体。
  2. noCopy 类型实现了两个方法:LockUnlock,而且都是空方法(即 no-op)。
  3. 注释中强调了 LockUnlockgo vet 检查时起作用。

noCopy 类型没有任何实际功能属性,要理解它的作用,必须从防止结构体被拷贝的需求入手,并探究为什么“must not be copied after first use”。

1.2 go vet 和 "locks erroneously passed by value"

我们可以通过以下命令查看 go vet 中的 copylocks 检查:

go tool vet help copylocks

go vet 告诉我们,包含锁的值在传递时可能会导致问题。举例:

package main

import (
    "fmt"
    "sync"
)

type T struct {
    lock sync.Mutex
}

func (t T) Lock() {
    t.lock.Lock()
}
func (t T) Unlock() {
    t.lock.Unlock()
}

func main() {
    var t T
    t.Lock()
    fmt.Println("test")
    t.Unlock()
    fmt.Println("finished")
}

此代码会输出错误,因为 LockUnlock 方法使用了值接收者,导致 T 被复制,每次调用方法时实际上是对副本加锁解锁。

修复方法: 使用指针接收者,确保操作的是同一个实例:

func (t *T) Lock() {
    t.lock.Lock()
}
func (t *T) Unlock() {
    t.lock.Unlock()
}

同样,在使用 WaitGroup 时,我们也必须确保不拷贝它的值。如下是一个错误的使用例子:

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, wg) // passes lock by value
    }

    wg.Wait()
    fmt.Println("All workers done!")
}

修复方法: 传递指针以使用同一个 WaitGroup 实例:

go worker(i, &wg)

1.3 尝试 go vet 检测

go vet 中的 noCopy 设计是一种防止结构体被拷贝的机制,特别适用于包含同步原语的结构体(如 sync.Mutexsync.WaitGroup)。以下是一个简单的 go vet 检测示例:

package main

import "fmt"

type noCopy struct{}

func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type noCopyData struct {
    Val int32
    noCopy
}

func main() {
    c1 := noCopyData{Val: 10}
    c2 := c1
    c2.Val = 20
    fmt.Println(c1, c2)
}

尽管代码运行正常,但 go vet 会提示 "passes lock by value"。这是因为 noCopy 的存在,确保开发者意识到潜在的拷贝问题。

1.4 其他 noCopy 策略

除了 go vet 的静态检测,Go 语言中也有一些更强制的防拷贝机制。以 strings.Builder 为例,它在运行时检查是否发生了非法拷贝:

// A Builder is used to efficiently build a string using [Builder.Write] methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

func (b *Builder) copyCheck() {
    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()
    b.buf = append(b.buf, p...)
    return len(p), nil
}

这段代码通过 b.addr 的自引用来防止 Builder 被拷贝。

1.5 总结

  1. 同步原语(如 sync.Mutexsync.WaitGroup)不应被拷贝,否则会引发并发问题。
  2. noCopy 提供了一种非强制的防拷贝机制,通过 go vet 工具进行静态检测。
  3. 某些 Go 源代码会在运行时进行更严格的检查,例如 strings.Buildersync.Cond,一旦检测到拷贝行为,将触发 panic
复制全文 生成海报 Golang 并发 编程技巧

推荐文章

ElasticSearch 结构
2024-11-18 10:05:24 +0800 CST
在 Rust 生产项目中存储数据
2024-11-19 02:35:11 +0800 CST
H5抖音商城小黄车购物系统
2024-11-19 08:04:29 +0800 CST
JS 箭头函数
2024-11-17 19:09:58 +0800 CST
宝塔面板 Nginx 服务管理命令
2024-11-18 17:26:26 +0800 CST
解决 PHP 中的 HTTP 请求超时问题
2024-11-19 09:10:35 +0800 CST
php内置函数除法取整和取余数
2024-11-19 10:11:51 +0800 CST
PHP 代码功能与使用说明
2024-11-18 23:08:44 +0800 CST
如何在 Vue 3 中使用 Vuex 4?
2024-11-17 04:57:52 +0800 CST
Linux 常用进程命令介绍
2024-11-19 05:06:44 +0800 CST
markdowns滚动事件
2024-11-19 10:07:32 +0800 CST
向满屏的 Import 语句说再见!
2024-11-18 12:20:51 +0800 CST
MySQL设置和开启慢查询
2024-11-19 03:09:43 +0800 CST
一个有趣的进度条
2024-11-19 09:56:04 +0800 CST
PostgreSQL日常运维命令总结分享
2024-11-18 06:58:22 +0800 CST
开源AI反混淆JS代码:HumanifyJS
2024-11-19 02:30:40 +0800 CST
实现微信回调多域名的方法
2024-11-18 09:45:18 +0800 CST
api远程把word文件转换为pdf
2024-11-19 03:48:33 +0800 CST
程序员茄子在线接单