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

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

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

推荐文章

Rust 并发执行异步操作
2024-11-18 13:32:18 +0800 CST
CSS Grid 和 Flexbox 的主要区别
2024-11-18 23:09:50 +0800 CST
Vue3中的组件通信方式有哪些?
2024-11-17 04:17:57 +0800 CST
Boost.Asio: 一个美轮美奂的C++库
2024-11-18 23:09:42 +0800 CST
Graphene:一个无敌的 Python 库!
2024-11-19 04:32:49 +0800 CST
Vue3的虚拟DOM是如何提高性能的?
2024-11-18 22:12:20 +0800 CST
Vue3中如何实现状态管理?
2024-11-19 09:40:30 +0800 CST
Vue3中的JSX有什么不同?
2024-11-18 16:18:49 +0800 CST
Vue3 结合 Driver.js 实现新手指引
2024-11-18 19:30:14 +0800 CST
联系我们
2024-11-19 02:17:12 +0800 CST
Nginx 防盗链配置
2024-11-19 07:52:58 +0800 CST
利用Python构建语音助手
2024-11-19 04:24:50 +0800 CST
随机分数html
2025-01-25 10:56:34 +0800 CST
程序员茄子在线接单