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

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

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

推荐文章

Vue3结合Driver.js实现新手指引功能
2024-11-19 08:46:50 +0800 CST
纯CSS实现3D云动画效果
2024-11-18 18:48:05 +0800 CST
如何在 Vue 3 中使用 TypeScript?
2024-11-18 22:30:18 +0800 CST
Plyr.js 播放器介绍
2024-11-18 12:39:35 +0800 CST
Golang Sync.Once 使用与原理
2024-11-17 03:53:42 +0800 CST
Go语言中实现RSA加密与解密
2024-11-18 01:49:30 +0800 CST
js迭代器
2024-11-19 07:49:47 +0800 CST
一个简单的打字机效果的实现
2024-11-19 04:47:27 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
File 和 Blob 的区别
2024-11-18 23:11:46 +0800 CST
html一个包含iPhoneX和MacBook模拟器
2024-11-19 08:03:47 +0800 CST
robots.txt 的写法及用法
2024-11-19 01:44:21 +0800 CST
如何在Rust中使用UUID?
2024-11-19 06:10:59 +0800 CST
在JavaScript中实现队列
2024-11-19 01:38:36 +0800 CST
html5在客户端存储数据
2024-11-17 05:02:17 +0800 CST
开发外贸客户的推荐网站
2024-11-17 04:44:05 +0800 CST
程序员茄子在线接单