目录

go grpc proto message 如何进行深拷贝

背景

数据拷贝在开发过程中非常常见,在 go 语言中如果是简单的对指针对象进行引用,那么修改新对象会同时影响引用对象,这是我们不想看到的

浅拷贝

(和拷贝的对象是同一块数据地址)拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址

// 原始消息
req := &pb.UserRequest{Id: 1001, Name: "张三"}

// ❌ 错误拷贝方式
copyReq := req
copyReq.Name = "李四"  // 原始req也被修改!

// ✅ 深拷贝后修改
safeCopy := DeepCopy(req)
safeCopy.Name = "王五" // 原始req不受影响

深拷贝

(全新独立对象)拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

方案一:proto.Clone(官方推荐)

import "google.golang.org/protobuf/proto"

func ProtoClone(src proto.Message) proto.Message {
    return proto.Clone(src)
}

方案二:序列化法(通用但低效)

func MarshalCopy(src proto.Message) proto.Message {
    data, _ := proto.Marshal(src)
    dst := reflect.New(reflect.TypeOf(src).Elem()).Interface().(proto.Message)
    proto.Unmarshal(data, dst)
    return dst
}

常见问题

1. 为什么不能用 json 序列化反序列化的方式来实现深拷贝 proto message

proto 的实现原理和 json 不一样,如果用 json 进行深拷贝,会出现数据丢失、数据精度等问题,而且还存在性能问题,不建议使用,建议使用官方推荐的 Marshal、Unmarshal、Clone 函数