golang 使用总结(1)

golang 使用总结(1),第1张

文章目录 1. 写在最前面2. 锁拷贝问题2.1 问题2.2 原因 3. 安全的 marshal/ unmarshal 含有锁的结构3.1 问题3.2 解决 4. 将 Proto 序列化的结构放入到 json 的字段中4.1 问题4.2 解决 5. 碎碎念6. 参考资料

1. 写在最前面

笔者最近在赶一个项目,在开发的过程中遇到了几处比较有趣的地方。做个笔记记录,同时方便其人查阅、快速解决类似的问题。

锁拷贝问题(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).

大意是:用一位伟大诗人的话来说:所以不要那样做。

可能编写一个复制函数,只复制非互斥对象的字段(您可能希望首先获得互斥对象)。

注:而且 go sync package 已经声明 Values containing the types defined in this package should not be copied.

2.2 原因

思考:你确定你真的需要拷贝吗?

答:拷贝 *** 作将拷贝内部状态,这会导致很多麻烦(例如互斥锁)。 创建一个具有相同的、外部可见的状态的新对象要简单得多。

错误拷贝状态的例子

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!

注:b.A 的地址跟 a 的地址,但是由于拷贝了 a 的锁,所以在 b.A 再次上锁的时候,导致了崩溃。

3. 安全的 marshal/ unmarshal 含有锁的结构 3.1 问题

问题场景是自定一个结构,其中还有一个 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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/990371.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存