笔者最近在赶一个项目,在开发的过程中遇到了几处比较有趣的地方。做个笔记记录,同时方便其人查阅、快速解决类似的问题。
锁拷贝问题(Mutex Copy )安全的 marshal/ unmarshal 含有锁的结构(Safe marshal/unmarshal structure with lock)将 Proto 序列化的结构放入到 json 的字段中(Put the structure of the Proto serialization into the json field) 2. 锁拷贝问题 2.1 问题在 「Google Groups」 里有人提里一个问题:How to copy a struct which contains a mutex?
此处引用 Staven 的回答:
In the words of a great poet: so don’t do that.
Possibly write a copying function that copies only fields that aren’t
a mutex (you probably want to acquire the mutex first).
大意是:用一位伟大诗人的话来说:所以不要那样做。
可能编写一个复制函数,只复制非互斥对象的字段(您可能希望首先获得互斥对象)。
2.2 原因注:而且 go sync package 已经声明 Values containing the types defined in this package should not be copied.
思考:你确定你真的需要拷贝吗?
答:拷贝 *** 作将拷贝内部状态,这会导致很多麻烦(例如互斥锁)。 创建一个具有相同的、外部可见的状态的新对象要简单得多。
错误拷贝状态的例子
package main
import (
"fmt"
"sync"
)
type A struct {
Lock sync.Mutex
X int
}
type B struct {
A
Y int
}
func newA(x int) A {
return A{X: x}
}
func newB(x int, y int) B {
a := newA(x)
a.Lock.Lock()
fmt.Printf("newB a address: %p, %#v\n", &a, &a)
return B{
A: a,
Y: y,
}
}
func main() {
b := newB(-1, 3)
fmt.Printf("main b.A address %p, %#v\n", &b.A, &b.A)
b.A.Lock.Lock()
}
输出:
mutex $>go run main.go
newB a address: 0x140000140c0, &main.A{Lock:sync.Mutex{state:1, sema:0x0}, X:-1}
main b.A address 0x14000018108, &main.A{Lock:sync.Mutex{state:1, sema:0x0}, X:-1}
fatal error: all goroutines are asleep - deadlock!
3. 安全的 marshal/ unmarshal 含有锁的结构 3.1 问题注:b.A 的地址跟 a 的地址,但是由于拷贝了 a 的锁,所以在 b.A 再次上锁的时候,导致了崩溃。
问题场景是自定一个结构,其中还有一个 string 字段和一个锁字段。问如何在并发 set/get string 字段的同时,线程安全的对该结构体 marshal/ unmarshal ?
定义的结构如下:
type Status struct {
AudioErrorMutex *sync.Mutex
AudioError string `json:"audioError"`
}
func (s *Status) GetAudioError() string {
s.AudioErrorMutex.Lock()
defer s.AudioErrorMutex.Unlock()
return s.AudioError
}
func (s *Status) SetAudioError(e string) {
s.AudioErrorMutex.Lock()
defer s.AudioErrorMutex.Unlock()
s.AudioError = e
}
3.2 解决
但,为了防止递归调用,一种解决方法是先将 AudioError 单独拆为一个结构体,然后结构体自己实现 MarshalJSON/UnmarshalJSON 的方法。。
package main
import (
"encoding/json"
"fmt"
"sync"
)
type iterm struct {
AudioError string `json:"audioError"`
}
type Status struct {
AudioErrorMutex sync.Mutex
AudioError iterm `json:"audioError"`
}
func (s *Status) GetAudioError() string {
s.AudioErrorMutex.Lock()
defer s.AudioErrorMutex.Unlock()
return s.AudioError.AudioError
}
func (s *Status) SetAudioError(e string) {
s.AudioErrorMutex.Lock()
defer s.AudioErrorMutex.Unlock()
s.AudioError.AudioError = e
}
func (s *Status) MarshalJSON() ([]byte, error) {
s.AudioErrorMutex.Lock()
defer s.AudioErrorMutex.Unlock()
return json.Marshal(s.AudioError)
}
func (s *Status) UnmarshalJSON(data []byte) error {
var i iterm
err := json.Unmarshal(data, &i)
if err != nil {
return err
}
s.AudioErrorMutex.Lock()
s.AudioError = i
s.AudioErrorMutex.Unlock()
return nil
}
func main() {
s := Status{}
s.SetAudioError("hello")
fmt.Printf("%v\n", &s.AudioError)
d, err := json.Marshal(&s)
if err != nil {
panic(err)
}
var p Status
err = json.Unmarshal(d, &p)
if err != nil {
panic(err)
}
fmt.Printf("p:%v\n", &p.AudioError)
}
输出:
marshal $> go run main.go
&{hello}
p:&{hello}
4. 将 Proto 序列化的结构放入到 json 的字段中
4.1 问题
此项目中笔者有个需求是,将 proto marshal 的结果存入 json 的 string 中。
注:你要是非得问我为什么有这么奇怪的用法,那只能是因为历史原因了。
示例大概如下:
// json structure 定义
type Info struct {
Id uint64 `json:"id"`
Message string `json:"message"`
}
然后将定义好的 proto message marshal 之后的结果存入到 Info.Message 字段。
笔者整理了一个小的复现现场的例子
目录结构:
.
├── example
│ ├── example.pb.go
│ └── example.proto
├── go.mod
├── go.sum
├── json // 二进制文件
└── main.go
proto 文件 — example.proto
syntax = "proto3";
package example;
option go_package = "../example";
message DelayTime {
int32 vid = 2; // vid 信息
string taskId = 5; // 任务 id
string streamId = 6; // 流 id
int64 delayTime = 15; // 延迟时间,单位毫秒
string eventName = 16; // 上报事件类型
}
注:生成 example.pb.go 的方法
执行 protoc --proto_path=./example --go_out=./example ./example/example.proto
复现问题的 main.go 文件
package main
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
proto "example/jsonAndPb/example"
protoc "github.com/golang/protobuf/proto"
)
func md5V(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
type Hello1 struct {
Content string `json:"content"`
}
func Marshal1(b []byte) ([]byte, error) {
fmt.Println("marshal", md5V(string(b)))
h1 := Hello1{
Content: string(b),
}
b1, err := json.Marshal(h1)
if err != nil {
return nil, err
}
return b1, nil
}
func Unmarshal1(b []byte) error {
var h1 Hello1
err := json.Unmarshal(b, &h1)
if err != nil {
return err
}
fmt.Println("unmarsal1", md5V(h1.Content))
return nil
}
func main() {
r := proto.DelayTime{
Vid: 10086,
TaskId: "task_id",
EventName: "test",
DelayTime: 1000,
}
b, err := protoc.Marshal(&r)
if err != nil {
fmt.Println(err)
return
}
data, err := Marshal1(b)
if err != nil {
fmt.Println(err)
return
}
err = Unmarshal1(data)
if err != nil {
fmt.Println(err)
return
}
}
输出:
jsonAndPb $> go build -o json
jsonAndPb $> ./json
marshal1 9935f3909ef5048a7028e60078a87f97
unmarsal1 746d3b114729e5f709c4d87aec86d307
结论:
proto marshal 之后的二进制在 json marshal 之前是 9935f3909ef5048a7028e60078a87f97json unmarshal 之后是 746d3b114729e5f709c4d87aec86d307这会导致在 proto unmarshal 的时候报proto: cannot parse invalid wire-format data
4.2 解决
核心思想是将 proto marshal 的二进制 -> base64 编码 -> 放入 json 中。
注:此处用一个比较取巧的方法,直接修改 Hello1 的定义将 Content 的类型改为 []byte。
Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON value.(数组和切片值编码为 JSON 数组,除了 []byte 编码为 base64 编码字符串,nil 切片编码为空 JSON 值。)
修改后的 main.go 文件
package main
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
proto "example/jsonAndPb/example"
protoc "github.com/golang/protobuf/proto"
)
func md5V(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
type Hello2 struct {
Content []byte `json:"content"`
}
func Marshal2(b []byte) ([]byte, error) {
fmt.Println("marshal2", md5V(string(b)))
h2 := Hello2{
Content: b,
}
b1, err := json.Marshal(h2)
if err != nil {
return nil, err
}
return b1, nil
}
func Unmarshal2(b []byte) error {
var h2 Hello2
err := json.Unmarshal(b, &h2)
if err != nil {
return err
}
fmt.Println("unmarsal2", md5V(string(h2.Content)))
return nil
}
func main() {
r := proto.DelayTime{
Vid: 10086,
TaskId: "task_id",
EventName: "test",
DelayTime: 1000,
}
b, err := protoc.Marshal(&r)
if err != nil {
fmt.Println(err)
return
}
data, err := Marshal2(b)
if err != nil {
fmt.Println(err)
return
}
err = Unmarshal2(data)
if err != nil {
fmt.Println(err)
return
}
}
输出结果:
jsonAndPb $> go build -o json
jsonAndPb $> ./json
marshal2 9935f3909ef5048a7028e60078a87f97
unmarsal2 9935f3909ef5048a7028e60078a87f97
5. 碎碎念
又进入了疫情爆发的阶段 ,自己和家人在的地方疫情都很严重。
前几天吹蜡烛的时候,悄悄许了一个愿望:「希望疫情能早点消失。」(ps 嗯,是被身边的人感动的一个月。
一切都是成长,包括热泪盈眶。我们终其一生都在成长,愿都能眼里有光,活成自己喜欢的模样。 6. 参考资料What is the best way to hide struct fields and yet make it synchronise access and modification of fields?
How to copy a struct which contains a mutex?
Go Do not copy me
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)