为什么 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 仅嵌入具体消息中,不嵌入实现原型消息的生成结构中。
它具体嵌入了三个pragmas:NoUnkeyedLiterals
、DoNotCompare
和DoNotCopy
。
最后一个,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 互斥量的三个原因:
- 维护者将来可能希望以一种不适用于浅拷贝的方式更改内部表示。
- 理论上混合对同一内存的原子访问和非原子访问是不安全的。 protobuf 结构包含一个内部字段,该字段通常通过原子访问进行读写。在消息上调用
msg.Marshal()
,然后用 *msg = MyMessage{...}
覆盖它混合了原子访问和非原子访问。即使这适用于今天在 x86 上的实现,也不能保证它将来也适用于其他系统。 (参见 a long Go issue about this)。
- 如果您在消息上调用
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
在 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 仅嵌入具体消息中,不嵌入实现原型消息的生成结构中。
它具体嵌入了三个pragmas:NoUnkeyedLiterals
、DoNotCompare
和DoNotCopy
。
最后一个,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 互斥量的三个原因:
- 维护者将来可能希望以一种不适用于浅拷贝的方式更改内部表示。
- 理论上混合对同一内存的原子访问和非原子访问是不安全的。 protobuf 结构包含一个内部字段,该字段通常通过原子访问进行读写。在消息上调用
msg.Marshal()
,然后用*msg = MyMessage{...}
覆盖它混合了原子访问和非原子访问。即使这适用于今天在 x86 上的实现,也不能保证它将来也适用于其他系统。 (参见 a long Go issue about this)。 - 如果您在消息上调用
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