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

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

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 并发 编程技巧

推荐文章

mendeley2 一个Python管理文献的库
2024-11-19 02:56:20 +0800 CST
联系我们
2024-11-19 02:17:12 +0800 CST
利用Python构建语音助手
2024-11-19 04:24:50 +0800 CST
批量导入scv数据库
2024-11-17 05:07:51 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
php内置函数除法取整和取余数
2024-11-19 10:11:51 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
windows安装sphinx3.0.3(中文检索)
2024-11-17 05:23:31 +0800 CST
html夫妻约定
2024-11-19 01:24:21 +0800 CST
filecmp,一个Python中非常有用的库
2024-11-19 03:23:11 +0800 CST
快手小程序商城系统
2024-11-25 13:39:46 +0800 CST
mysql时间对比
2024-11-18 14:35:19 +0800 CST
基于Webman + Vue3中后台框架SaiAdmin
2024-11-19 09:47:53 +0800 CST
Linux 常用进程命令介绍
2024-11-19 05:06:44 +0800 CST
程序员茄子在线接单