为什么 Go 生成的 protobuf 文件包含互斥锁?

Why do the Go-generated protobuf files contain mutex locks?

在 go 中工作和生成 protobuf 存根时,我偶然发现了这个有趣的问题。

每当我尝试按值复制消息的结构时,我都会收到此警告:

call of state.world.script.HandleEvent copies lock value: throne/server/messages.PlayerDialogeStatus contains google.golang.org/protobuf/internal/impl.MessageState contains sync.Mutex copylocks

虽然 我明白为什么按值复制互斥锁是错误的,但我开始想知道为什么它们一开始就在那里。

因此我的问题是:为什么 go 生成的 protobuf 文件包含放置在消息结构上的互斥锁,特别是在 MessageState 结构上?

或者:放置在生成的 protobuf 消息结构上的 MessageState 结构中的互斥锁的目标是什么?

impl.MessageState 仅嵌入具体消息中,不嵌入实现原型消息的生成结构中。

它具体嵌入了三个pragmasNoUnkeyedLiteralsDoNotCompareDoNotCopy

最后一个,DoNotCopy是zero-sized个sync.Mutex数组。唯一的目的是 go vet 大声抱怨浅拷贝,如评论中所述:

DoNotCopy can be embedded in a struct to help prevent shallow copies. This does not rely on a Go language feature, but rather a special case within the vet checker.

总结一下:impl.MessageState 不应该被复制,互斥锁只是为了捕获复制。如果你这样做,那是因为你用错了东西。

据我所知,Go protobuf API 包含 DoNotCopy 互斥量的三个原因:

  1. 维护者将来可能希望以一种不适用于浅拷贝的方式更改内部表示。
  2. 理论上混合对同一内存的原子访问和非原子访问是不安全的。 protobuf 结构包含一个内部字段,该字段通常通过原子访问进行读写。在消息上调用 msg.Marshal(),然后用 *msg = MyMessage{...} 覆盖它混合了原子访问和非原子访问。即使这适用于今天在 x86 上的实现,也不能保证它将来也适用于其他系统。 (参见 a long Go issue about this)。
  3. 如果您在消息上调用 ProtoReflect(),然后稍后覆盖该消息,它将崩溃,因为 ProtoReflect() 结果依赖于内部反射指针 (original issue):
d := &durationpb.Duration{Seconds: 1}
protoreflectMessage := d.ProtoReflect()
fmt.Printf("protoreflectMessage.Interface()=%v\n", protoreflectMessage.Interface())
*d = durationpb.Duration{Seconds: 2}
fmt.Printf("protoreflectMessage.Interface()=%v\n", protoreflectMessage.Interface())

这会崩溃:

protoreflectMessage.Interface()=seconds:1
panic: invalid nil message info; this suggests memory corruption due to a race or shallow copy on the message struct