Golang之protobuf、WaitGroup、benchmark
Protobuf基础使用
使用:安装protoc、protoc-gen-go
定义消息类型
syntax = "proto3";
package main;
option go_package="./;main";
message Student {
string name = 1;
bool male = 2;
repeated int32 scores = 3; // []int类型
}
生成对应的go代码
protoc --go_out=. student.proto
测试使用
package main
import (
"fmt"
"github.com/golang/protobuf/proto"
"log"
)
func main() {
student := &Student{
Name: "hqinglau",
Male: true,
Scores: []int32{1, 2, 3},
}
data, err := proto.Marshal(student)
if err != nil {
log.Fatal("marshaling error: ", err)
}
var newStudent Student
err = proto.Unmarshal(data, &newStudent)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
fmt.Printf("%+v", student)
fmt.Printf("%+v", newStudent)
}
修饰符
required:必须字段,不写默认required
optional:可选字段,更新可以用这个,不影响老版本
repeated:数组概念
更新消息
message Student {
string name = 1;
bool male = 2;
repeated int32 scores = 3; // []int类型
optional string email = 4;
}
老代码仍可运行,也可写新代码:
package main
import (
"fmt"
"github.com/golang/protobuf/proto"
"log"
)
func main() {
email := "hqinglau@gmail.com"
student := &Student{
Name: "hqinglau",
Male: true,
Scores: []int32{1, 2, 3},
Email: &email,
}
data, err := proto.Marshal(student)
if err != nil {
log.Fatal("marshaling error: ", err)
}
var newStudent Student
err = proto.Unmarshal(data, &newStudent)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
fmt.Printf("%+v\n", student)
fmt.Printf("%+v\n", newStudent.GetEmail())
}
//name:"hqinglau" male:true scores:1 scores:2 scores:3 email:"hqinglau@gmail.com"
//hqinglau@gmail.com
WaitGroup使用陷阱
正确示例
package waitGroup
import (
"fmt"
"sync"
"testing"
)
func worker(msg string) {
fmt.Printf("worker do %s\n", msg)
}
func TestWorker(t *testing.T) {
waitGroup := &sync.WaitGroup{}
for i:=0; i<3; i++ {
waitGroup.Add(1)
go func(k int) {
defer waitGroup.Done()
worker(fmt.Sprintf("task %d", k))
}(i)
}
waitGroup.Wait()
fmt.Println("main exit")
}
错误示例
错误代码:
var wg sync.WaitGroup
func worker(msg string) {
wg.Add(1)
defer wg.Done()
fmt.Printf("worker do %s\n", msg)
}
func main() {
go worker("task 1")
go worker("task 2")
go worker("task 3")
fmt.Println("waiting")
wg.Wait()
fmt.Println("main exit")
}
错误原因:
WaitGroup
调用Wait
时,可能就没进入协程,没Add
呢,直接就退出了。
Benchmark基准测试
func BenchmarkSprint(b *testing.B) { // 测试基准的函数需以Benchmark开头, 参数testing.B
b.ResetTimer() // 重置计时器,防止前面有代码,造成干扰
// b.N 从N开始,递增(越到后面增加越快),如果用例能够在1s内结束,就会增加
for i:=0; i<b.N; i++ {
fmt.Sprint(i)
}
}
//goos: windows
//goarch: amd64
//cpu: Intel(R) Core(TM) i3-9100F CPU @ 3.60GHz
//BenchmarkSprint
//BenchmarkSprint-4 13386700 86.35 ns/op
//PASS
数组和切片
go
数组是值类型,复制会复制整个数组。
func TestArray(t *testing.T) {
a := [...]int{1,2,3}
b := a
b[0] = 999
fmt.Println(b) // [999 2 3]
fmt.Println(a) // [1 2 3]
}
切片陷阱
例如有个很大的切片,但是现在我只需要其中的最后两位,此时如果直接用切片origin[len(origin)-2:]
,原切片的引用还会保留,原切片不能释放内存;此时若用复制,则可以释放原切片内存。
t.Helper()
的作用是标记一个函数为测试辅助函数,这样的话,该函数将不会在测试日志输出文件名和行号信息时出现。当 go testing 系统在查找调用栈帧的时候,通过 Helper 标记过的函数将被略过,因此这有助于找到更确切的调用者及其相关信息。这个函数的用途在于削减日志输出中(尤其是在打印调用栈帧信息时)的杂音。
func lastTwoBySlice(origin []int) []int {
return origin[len(origin)-2:]
}
func lastTwoByCopy(origin []int) []int {
ret := make([]int,2)
copy(ret, origin)
return ret
}
func generateRandomSlice(n int) []int {
ret := make([]int, n)
for i:=0;i<n;i++ {
ret[i] = rand.Int()
}
return ret
}
func printMem(t *testing.T) {
t.Helper()
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
t.Logf("%.2f MB", float64(rtm.Alloc)/1024./1024.)
}
func testLastChars(t *testing.T, f func([]int) []int) {
t.Helper()
ans := make([][]int, 0)
for k := 0; k < 100; k++ {
origin := generateRandomSlice(128 * 1024) // 1M
ans = append(ans, f(origin))
runtime.GC()
}
printMem(t)
_ = ans
}