源码地址: https://gitee.com/geektime-geekbang/go_learning or https://github.com/geektime-geekbang/go_learning/
Go 语言从入门到实战 笔记 基础部分 差异与基础 退出返回值
Go中main函数不支持任何返回值
通过os.Exit来返回状态
获取命令行参数
main函数不支持传入参数
在程序中直接用过os.Args获取命令行参数
变量赋值
赋值可以进行自动类型推断
在一个赋值语句中可以对多个变量进行同时赋值
快速设置连续值 1 2 3 4 5 6 7 8 9 const ( Monday = iota + 1 Tuesday Wednesday Thursday Friday Saturday Sunday )
1 2 3 4 5 const ( Open = 1 << iota Close Pending )
类型转化
Go语言不允许隐式类型转换
别名和原有类型也不能进行隐式类型转换
指针类型
不支持指针运算
string是值类型,其默认得初始化值为空字符串,而不是nil
算术运算符
Go语言没用前置得++,–, (++a)
用==比较数组
相同维数且含有相同个数元素得的数组才可以比较
每个元素都相同的才相等
位运算符,&^按位置零 1 &^ 0 -- 11 &^ 1 -- 00 &^ 1 -- 00 &^ 0 -- 0
循环 while 条件循环 while (n<5)
1 2 3 4 5 n := 0 for n < 5{ n++ fmt.Println(n) }
无限循环 while(true)
if条件
condition表达式结果必须为布尔值
支持变量赋值:1 2 3 if var declaration; condition { }
switch条件
条件表达式不限制为常量或者整数;
单个case中,可以出现多个结果选项,是用逗号分隔;
与C语言等规则相反,Go语言不需要用break来明确退出一个case;
可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if...else...的逻辑作用等同
1 2 3 4 5 6 7 8 switch os := runtime.GOOS;os{ case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: fmt.Println("%s.", os) }
1 2 3 4 5 6 7 8 switch{ case 0 <= Num && Num <= 3: fmt.Printf("0-3") case 4 <= Num && Num <= 6: fmt.Printf("4-6") case 7 <= Num && Num <= 9: fmt.Printf("7-9") }
切片 切片声明: 1 2 3 4 5 6 7 8 9 10 11 12 var s0[] int // 数组中[]需要填值或者...填充数 s0 = append(s0, 1) s := []int{} s1 := []int{1,2,3} s2 := nake([]int, 2, 4) /* type, len, cap 其中len个元素会被初始化为默认零值,未初始化元素不可以访问 */
切片如何实现可变长
Map声明 1 2 3 4 5 6 7 m := map[string]int{"one": 1, "two": 2, "three": 3} m1 = map[string]int{} m1["one"] = 1 m2 := make(map[string]int, 10/* 初始化cap*/)
Map元素的访问 在访问的key不存在时,仍会返回零值,不能通过返回nil来判断元素是否存在
1 2 3 4 5 6 7 8 m1 := map [int ]int {} if v, ok := m1[3 ]; ok { t.Logf("key 3`s value is %d" , v) t.Log(v, ok) } else { t.Log("key 3 is not existing." ) t.Log(v, ok) }
Map遍历 1 2 3 4 m1 := map [int ]int {1 : 1 , 2 : 4 , 3 : 9 } for k, v := range m1 { t.Log(k, v) }
Map与工厂模式
Map的value可以是一个方法
与Go的Dock type接口方式一起,可以方便的实现单一方法对象的工厂模式1 2 3 4 5 m := map[int]func(op int) int{} m[1] = func(op int) int { return op } m[2] = func(op int) int { return op * op } m[3] = func(op int) int { return op * op * op } t.Log(m[1](2), m[2](2), m[3](2))
实现Set Go的内置集合中没有Set实现,可以map[type]bool
==元素的唯一性==
基本操作
添加元素
判断元素是否存在
删除元素
元素个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 mySet := map [int ]bool {} mySet[1 ] = true n := 1 if mySet[n] { t.Logf("%d is existing" , n) } else { t.Logf("%d is not existing" , n) } mySet[3 ] = true t.Log(len (mySet)) delete (mySet, 1 )n = 1 if mySet[n] { t.Logf("%d is existing" , n) } else { t.Logf("%d is not existing" , n) }
字符串
string是数据类型,不是引用或指针类型
string是只读的byte slice, len函数可以查它所包含的byte数
string的byte数组可以存放任何数据
Unicode UTF8
Unicode 是一种字符集(code point)
UTF8 是 unicode 的存储实现(转换为字节系列的规则)
String转rune 1 2 3 4 s := "去隔壁吃辣条" for _, c := range s { t.Logf("%[1]c %[1]x %[1]d %[2]x", c, string(c)) // "[]"中的数字代表抵 }
常用的字符串函数
strings包
strconv包1 2 3 4 5 6 s := "A,B,C" parts := strings.Split(s, ",") for _, part := range parts { t.Log(part) } t.Log(strings.Join(parts, "-"))
1 2 3 4 5 s := strconv.Itoa(10) t.Log("str" + s) if i, err := strconv.Atoi("10"); err == nil { t.Log(10 + i) }
函数是一等公民
可以有多个返回值
所有参数都是值传递:slice, map, channel会有传引用的错觉
函数可以作为变量的值
函数可以作为参数和返回值
可变参数 1 2 3 4 5 6 7 8 9 10 11 12 13 func Sum(ops ...int) int { ret := 0 for _, op := range ops { ret += op } return ret } func TestVarParam(t *testing.T) { t.Log(Sum(1, 2, 3, 4)) t.Log(Sum(1, 2, 3, 4, 5)) }
defer函数1 2 3 4 5 6 7 8 9 10 11 func Clear () { fmt.Println("Clear resources." ) } func TestDefer (t *testing.T) { defer Clear() fmt.Println("Start" ) panic ("err" ) fmt.Println("End" ) }
Go的错误机制
没有异常机制
error 类型实现了 error接口
及早失败,避免嵌套!
1 2 3 type error interface { Error() string }
可以通过errors.New来快速创建实例errors.New("n must be in the range [0,10]")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var LessThanTwoError = errors.New("n should be not less than 2") var LargerThenHundredError = errors.New("n should be not larger than 100") func GetFibonacci(n int) ([]int, error) { if n < 2 { return nil, LessThanTwoError } if n > 100 { return nil, LargerThenHundredError } fibList := []int{1, 1} for i := 2; i < n; i++ { fibList = append(fibList, fibList[i-2]+fibList[i-1]) } return fibList, nil } func TestGetFibonacci(t *testing.T) { if v, err := GetFibonacci(-10); err != nil { if err == LessThanTwoError { fmt.Println("It is less.") } t.Error(err) } else { t.Log(v) } }
panic
panic用于不可以恢复的错误
panic退出前会执行defer指定的内容
panic VS os.Exit
os.Exit退出时不会调用defer指定的函数1 2 3 4 5 6 7 8 9 func TestPanicVxExit(t *testing.T) { defer func() { fmt.Println("Finally") }() fmt.Println("Start") os.Exit(-1) //panic(errors.New("something wrong")) //fmt.Println("End") }
==console:==
1 2 3 === RUN TestPanicVxExit Start
没打印Finally
捕获所有错误 recover
在GO语言中recover
1 2 3 4 5 defer func() { if err := recover(); err != nil { // 恢复错误 } }()
recover()返回的是panic的error例如:panic(error.New("错误信息"))
==使用recover需谨慎==
形成僵尸服务进程,导致health check失效
“Let it Crash!”往往是我们恢复不确定性错误的最好方法。
该退出重启时则退出重启(重启是恢复不确定性的最好办法,哈哈~ 建议:重启多次,则让程序自行退出),程序运行中别急着通过recover修复
GO的依赖管理 package
基本复用模块单元
以首字母大写来表明可被包外代码访问
代码的package可以和所在的目录不一致
同一目录里的Go代码的package要保持一致
remote package
通过get get来获取远程依赖
注意代码在Github上的组织形式,以适应go get
直接以代码路径开始,不要有src
国内需要设置以一下Go代理
1 go env -w GOPROXY=https://goproxy.cn,direct
例子
1 go get -u github.com/easierway/concurrent_map
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 package remote_package import ( cm "github.com/easierway/concurrent_map" "testing" ) func TestConcurrentMap(t *testing.T) { m := cm.CreateConcurrentMap(99) m.Set(cm.StrKey("key"), 10) t.Log(m.Get(cm.StrKey("key"))) }
init方法
在main被执行前,所有依赖的package的init方法都会被执行
不同包的init函数按照包导入的依赖关系决定执行顺序
每个包可以有多个init函数
包的每个源程序也可以有多个init函数 ,这点比较特殊
init函数的主要作用:
初始化不能采用初始化表达式初始化的变量。
程序运行前的注册。
实现sync.Once功能。
init函数的主要特点:
init函数先于main函数自动执行,不能被其他函数调用;
init函数没有输入参数、返回值; 每个包可以有多个init函数;
包的每个源文件也可以有多个init函数 ,这点比较特殊; -同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖 这个执行顺序。
不同包的init函数按照包导入的依赖关系决定执行顺序。
初始化顺序:变量初始化->init()->main()
Go未解决的依赖问题
同一环境下,不同项目使用同一包的不同版本
无法管理对包的特定版本的依赖
vendor路径 查找依赖包管理的解决方法如下
当前包下的vendor目录
向上级目录查找,直到找到src下的vendor目录
在GOPATH下面查找依赖包
在GOROOT目录下查找
常用的依赖管理工具
Go的交叉编译 1 2 3 4 5 6 我的电脑 17 :32 :12 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -p 2 xxxx.go CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o xxxx-linux-arm64 -p 2 xxxx.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -p 2 xxxx.go CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o xxxx-darwin-amd64 -p 2 xxxx.go CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o xxxx-darwin-arm64 -p 2 xxxx.go
并发编程 Thread VS Groutine
创建时默认的stack的大小
JDK5 以后 Java Thread stack 默认为1M
Groutine的stack初始化大小为2K
和KSE(Kernel Space Entity)的对应关系
Java Thread 是1:1
Groutine是M:N
共享内存并发机制 sync.Mutex 互斥锁
互斥:防止两条线程同时对同一公共资源(比如全域變數)进行读写的机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func TestCounterThreadSafe(t *testing.T) { var mut sync.Mutex counter := 0 for i := 0; i < 5000; i++ { go func() { defer func() { // 使用defer 是为了防止一些异常操作导致锁无法正确释放 mut.Unlock() }() mut.Lock() counter++ }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) }
sync.WaitGroup同步线程 类似java中的join1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func TestCounterWaitGroup(t *testing.T) { var mut sync.Mutex var wg sync.WaitGroup counter := 0 for i := 0; i < 5000; i++ { wg.Add(1) go func() { defer func() { mut.Unlock() }() mut.Lock() counter++ wg.Done() }() } wg.Wait() t.Logf("counter = %d", counter) }
CSP并发机制(Go语言特有的)
CSP (Communicating sequential processes) 通信顺序进程 CSP 是一种形式语言,用来描述并发性系统间进行交互的模式。它是叫做进程代数或进程演算的关于并发的数学理论家族的一员,基于了通过通道的消息传递
Actor Model
CSP vs Actor
和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些。
Go中channel是有容量限制并且独立于处理Groutine,而如Erlang,Actor模式中的mailbox容量是无限的,接受进程也总是被动地处理消息
Channel
多路选择和超时 多路选择
如果多个case同时就绪时,select会随机地选择一个执行,这样来保证每一个channel都有平等的被select的机会
1 2 3 4 5 6 7 8 select { case ret := <-retCh1: t.Logf("result %s", ret) case ret := <-retCh2: t.Logf("result %s", ret) default: t.Error("No one returned") }
超时控制 1 2 3 4 5 6 select { case ret := <-retCh: t.Logf("result %s", ret) case <-time.After(time.Second * 1): t.Error("time out") }
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func service() string { time.Sleep(time.Millisecond * 50) return "Done" } func otherTask() { fmt.Println("working on something else") time.Sleep(time.Millisecond * 100) fmt.Println("Task is done.") } func AsyncService() chan string { //retCh := make(chan string) retCh := make(chan string, 1) go func() { ret := service() fmt.Println("returned result.") retCh <- ret fmt.Println("service exited.") }() return retCh } func TestSelect(t *testing.T) { select { case ret := <-AsyncService(): t.Log(ret) case <-time.After(time.Millisecond * 100): t.Error("time out") } }
channel的关闭
向关闭的channel发送数据,会导致panic
v, ok <-ch为bool值,true表示正常接受,false表通道关闭
所有的channel接收者都会在channel关闭时,立即从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行向多个订阅者同时发送信号。
如:退出信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 /** waitGroup 保证协程运行完(防止程序执行完毕,协程还没执行的尴尬境地) */ func dataProducer(ch chan int, wg *sync.WaitGroup) { go func() { for i := 0; i < 10; i++ { count := rand.Intn(1000) time.Sleep(time.Millisecond * time.Duration(count)) ch <- i fmt.Println("发的时间", time.Now()) } close(ch) wg.Done() }() } func dataReceiver(ch chan int, wg *sync.WaitGroup) { //go func() { // for i := 0; i < 10; i++ { // if data, ok := <-ch; ok { // fmt.Println(data) // } else { // break // } // } // wg.Done() //}() go func() { for { if data, ok := <-ch; ok { fmt.Println("收的时间", time.Now()) fmt.Println(data) } else { break } } wg.Done() }() } func TestCloseChannel(t *testing.T) { var wg sync.WaitGroup ch := make(chan int) wg.Add(1) dataProducer(ch, &wg) wg.Add(1) dataReceiver(ch, &wg) //wg.Add(1) //dataReceiver(ch, &wg) wg.Wait() }
任务取消 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // 多路复用 func isCancelled(ch chan struct{}) bool { select { case <-ch: return true default: return false } } func cancel_1(cancelChan chan struct{}) { cancelChan <- struct{}{} } func cancel_2(cancelChan chan struct{}) { close(cancelChan) } func TestCancel(t *testing.T) { cancelChan := make(chan struct{}, 0) for i := 0; i < 5; i++ { go func(i int, cancelCh chan struct{}) { for { if isCancelled(cancelCh) { break } time.Sleep(time.Millisecond * 5) } fmt.Println(i, "Done") }(i, cancelChan) } cancel_2(cancelChan) time.Sleep(time.Second * 1) }
Context 与 任务取消
Context
根Context:通过context.Background()创建
子Context:context.WithCancel(parentContext)创建
ctx, cancel := context.WithCancel(context.Background())
cancel是一个方法
当前Context被取消时,基于他的子context都会被取消
接收取消通知 <-ctx.Done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func isCancelled(ctx context.Context) bool { select { case <-ctx.Done(): return true default: return false } } func TestCancelCloseByContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) for i := 0; i < 5; i++ { go func(i int, ctx context.Context) { for { if isCancelled(ctx) { break } time.Sleep(time.Millisecond * 5) } fmt.Println(i, "Cancelled") }(i, ctx) } cancel() time.Sleep(time.Second * 1) }
只运行一次 单例模式 (懒汉式,线程安全)
使用Once1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Singleton struct { } var singleInstance *Singleton var once sync.Once func GetSingletonObj() *Singleton { once.Do(func() { fmt.Println("Create obj") singleInstance = new(Singleton) }) return singleInstance } func TestSingletonObj(t *testing.T) { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { obj := GetSingletonObj() fmt.Printf("%p\n", obj) wg.Done() }() } wg.Wait() }
仅需任意任务完成
利用buffered channel的机制(容量满了才会阻塞),解决==无缓存channel==阻塞,导致协程无法释放,最后引起Memory Leak问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func runTask(id int) string { time.Sleep(10 * time.Millisecond) return fmt.Sprintf("The result is from %d", id) } func FirstResponse() string { numberOfRunner := 10 ch := make(chan string, numberOfRunner) for i := 0; i < numberOfRunner; i++ { go func(i int) { ret := runTask(i) ch <- ret }(i) } return <-ch } func TestFistResponse(t *testing.T) { t.Log("Before:", runtime.NumGoroutine()) t.Log(FirstResponse()) time.Sleep(time.Second * 1) t.Log("After:", runtime.NumGoroutine()) }
所有任务完成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func runTask(id int) string { time.Sleep(time.Millisecond * 10) return fmt.Sprintf("The result is from %d", id) } func AllResponse() string { numberOfRunner := 10 ch := make(chan string, numberOfRunner) //ch := make(chan string) res := "" for i := 0; i < numberOfRunner; i++ { go func(i int) { ret := runTask(i) ch <- ret }(i) } for i := 0; i < numberOfRunner; i++ { res += <-ch + "\n" } return res } func TestAllResponse(t *testing.T) { t.Log("Before:", runtime.NumGoroutine()) t.Log(AllResponse()) time.Sleep(time.Second * 1) t.Log("After:", runtime.NumGoroutine()) }
对象池
使用不同的池缓冲不同的对象Pool Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type ReusableObj struct { } type ObjPool struct { bufChan chan *ReusableObj // 用于缓冲可重用对象 } func NewObjPool(numberOfObj int) *ObjPool { objPool := ObjPool{} objPool.bufChan = make(chan *ReusableObj, 10) for i := 0; i < numberOfObj; i++ { objPool.bufChan <- &ReusableObj{} } return &objPool } func (p *ObjPool) GetObj(timeout time.Duration) (*ReusableObj, error) { select { case ret := <-p.bufChan: return ret, nil case <-time.After(timeout): // 超时控制 return nil, errors.New("time out") } } func (p *ObjPool) ReleaseObj(obj *ReusableObj) error { select { case p.bufChan <- obj: return nil default: return errors.New("overflow") } }
use Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func TestObjPool(t *testing.T) { pool := NewObjPool(10) if err := pool.ReleaseObj(&ReusableObj{}); err != nil { // 溢出 t.Log(err) } for i := 0; i < 11; i++ { if v, err := pool.GetObj(time.Second * 1); err != nil { t.Error(err) } else { fmt.Printf("%T\n", v) if err := pool.ReleaseObj(v); err != nil { t.Error(err) } } } fmt.Println("Done") }
sync.Pool对象缓存sync.Pool对象获取
尝试从私有对象获取
私有对象不存在,尝试从当前Processor的共享池获取
如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取
如果所有子池都是空的,最后就用用户指定的New函数产生一个新的对象返回
协程安全 既没有锁,有锁便是协程不安全
sync.Pool对象的返回
如果私有对象不存在则保存为私有对象
如果私有对象蹲在,放入当前Processor子池的共享池
使用sync.Pool 1 2 3 4 5 6 7 8 9 pool := &sync.Pool{ New: func() interface{} { return 0 }, } array := pool.Get().(int) ... pool.Put(10)
sync.Pool对象的生命周期
GC会清楚sync.pool缓存的对象
对象的缓存有效期为下一次GC之前
sync.Pool总结
适合于通过复用、降低复杂对象得创建和GC代价
协程安全.==会有锁的开销==
==生命周期受GC影响,不适合于做连接池等,需自己管理生命周期的资源的池化==
是锁的开销大 与 创建对象得开销大,决定 sync.Pool是否带来性能优化得功效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func TestSyncPool(t *testing.T) { pool := &sync.Pool{ New: func() interface{} { fmt.Println("Create a new object.") return 100 }, } v := pool.Get().(int) fmt.Println(v) pool.Put(3) runtime.GC() // GC 会清除sync.pool 中缓存的对象 v1, _ := pool.Get().(int) fmt.Println(v1) v2, _ := pool.Get().(int) fmt.Println(v2) } func TestSyncPoolInMultiGroutine(t *testing.T) { pool := &sync.Pool{ New: func() interface{} { fmt.Println("Create a new object.") return 10 }, } pool.Put(100) pool.Put(100) pool.Put(100) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { //t.Log(pool.Get()) fmt.Println(pool.Get()) wg.Done() }(i) } wg.Wait() }
Go中的面向对象 封装数据和行为
Go中的接口使用Duck Type(鸭子类型)设计类型
结构体定义 1 2 3 4 5 type Employee struct{ Id string Name string Age int }
结构体创建及初始化
1 2 3 4 5 6 7 e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) // 注意这里返回的引用/指针,相当于 e := &Employee{} e2.Id = "2" // 与其他主要编程语言的差异:通过实例的指针访问成员不需要试用-> e2.Age = 22 e2.Name = "Rose"
行为(方法)定义
1 2 3 func (e Employee) String() string { return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) }
第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
1 2 3 func (e *Employee) String() string { return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) }
通常情况下为了避免内存拷贝,会是使用上面这种定义方式
Duck Type式接口实现接口定义
1 2 3 type Programmer interface { WriteHelloWorld() string }
接口实现
1 2 3 4 5 6 type GoProgrammer struct {} func (g *GoProgrammer) WriteHelloWorld() string { return "fmt.Println(\"Hello World\")" }
接口实现检测
编译期就检查接口是否实现(Golang直接会显示错误,如果没实现),不用等在使用时候才检测
1 var _ GoProgrammer = (*Programmer)(nil )
Go接口与其他主流编程语言的差异
接口为非入侵性,实现不依赖于接口定义
所以接口的定义可以包含在接口使用者的包内(不会产生循环依赖)
自定义类型
type IntCovertionFn func(n int) int
type MyPoint int1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type IntConv func (op int ) int func timeSpent (inner IntConv) IntConv { return func (op int ) int { start := time.Now() ret := inner(op) fmt.Println("time spent:" , time.Since(start).Seconds()) return ret } } func slowFn (op int ) int { time.Sleep(time.Second * 1 ) return op } func TestSlowFn (t *testing.T) { fn := timeSpent(slowFn) t.Log(fn(10 )) }
扩展与复用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 type Pet struct { } func (p *Pet) Speak() { fmt.Print("...") } func (p *Pet) SpeakTo(host string) { p.Speak() fmt.Println(" ", host) } type Dog struct { //p *Pet Pet } //func (d *Dog) Speak() { // //d.p.Speak() // fmt.Print("Wang!") //} // //func (d *Dog) SpeakTo(host string) { // //d.p.SpeakTo(host) // d.Speak() // fmt.Println(" ", host) //} func (d *Dog) Speak() { fmt.Println("Wang!") } func TestDog(t *testing.T) { dog := new(Dog) dog.SpeakTo("Chao") }
多态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 type Code string type Programmer interface { WriteHelloWorld() Code } type GoProgrammer struct { } func (p *GoProgrammer) WriteHelloWorld() Code { return "fmt.Println(\"Hello World!\")" } type JavaProgrammer struct { } func (p *JavaProgrammer) WriteHelloWorld() Code { return "System.out.Println(\"Hello World\")" } func writeFirstProgram(p Programmer) { fmt.Printf("%T %v\n\n", p, p.WriteHelloWorld()) } func TestPolymorphism(t *testing.T) { goProg := new(GoProgrammer) goProg2 := &GoProgrammer{} javaProg := new(JavaProgrammer) writeFirstProgram(goProg) writeFirstProgram(javaProg) writeFirstProgram(goProg2) }
空接口与断言
空接口可以表示==任何类型==
通过断言来将空接口转换为制定类型v,ok := p.(int) // ok=true时为转换成功
断言用法: *.(type)中的.()是格式type是被断言的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func DoSomething(p interface{}) { //if i, ok := p.(int); ok { // 这是断言 // fmt.Println("Interger", i) // return //} //if s, ok := p.(string); ok { // fmt.Println("string", s) // return //} //fmt.Println("Unknow Type") switch v := p.(type) { case int: fmt.Println("Integer", v) case string: fmt.Println("string", v) default: fmt.Println("Unknow Type") } } func TestEmptyInterfaceAssertion(t *testing.T) { DoSomething(10) DoSomething("10") }
测试 测试testing
源码文件以_test结尾:xxx_test.go
测试文件名以Test开头:func TestXXX(t *testing.T){...}
大写的方法代表==包外==可以访问
单元测试 表格测试法
准备一组输入组合,对期待值组合进行一对一比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func square(op int) int { return op * op } func TestSquare(t *testing.T) { inputs := [...]int{1, 2, 3} expected := [...]int{1, 4, 9} for i := 0; i < len(inputs); i++ { ret := square(inputs[i]) if ret != expected[i] { t.Errorf("input is %d, the expected is %d, the actual %d", inputs[i], expected[i], ret) } } }
内置单元测试框架
BenchMark
代码片段测评
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func BenchmarkConcatStringByAdd(b *testing.B) { elems := []string{"1", "2", "3", "4", "5"} ret := "" b.ResetTimer() for i := 0; i < b.N; i++ { for _, elem := range elems { ret += elem } } b.StopTimer() } func BenchmarkConcatStringBytesBuffer(b *testing.B) { var buf bytes.Buffer elems := []string{"1", "2", "3", "4", "5"} b.ResetTimer() for i := 0; i < b.N; i++ { for _, elem := range elems { buf.WriteString(elem) } } b.StopTimer() }
go test -bench=. -benchmem
-bench=<相关benchmark测试> window下使用go test命令行时,-bench=.应写为-bench="."
1 2 3 4 5 6 7 8 9 go test -bench="." -benchmem goos: windows goarch: amd64 pkg: testingOperation/unitTest cpu: AMD Ryzen 7 3700X 8-Core Processor BenchmarkConcatStringByAdd-16 10998074 108.9 ns/op 16 B/op 4 allocs/op BenchmarkConcatStringBytesBuffer-16 25526863 48.09 ns/op 64 B/op 1 allocs/op PASS ok testingOperation/unitTest 2.721s
16: 表示允许时对应的GOMAXPROCS的值
10998074: 表示运行for循环的次数,也就是调用被测试代码的次数,也就是b.N的范围内执行的次数
108.9 ns/op:表示平均每次需要花费108.9纳秒
allocs/op:表示每个参数(单次迭代)发生了多少不同的内存分配
B/op: 每个操作分配了多少字节
BDD
Behavior Driven Development
项目网站 :https://github.com/smartystreets/goconvey
安装 go get -u github.com/smartystreets/goconvey/convey
启动 WEB UI $GOPATH/bin/goconvey
需要完整安装go get -u github.com/smartystreets/goconvey
反射编程 reflect.TypeOf vs reflect.ValueOf
reflect.TypeOf返回类型(reflect.Type)
reflect.ValueOf返回值(reflect.Value)
可以从reflect.Value获得类型
通过kind的来判断类型
判断类型-kind() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 ... )
利用反射编写灵活的代码 按名字访问结构的成员 reflect.ValueOf(*e).FieldByName("Name")
按名字访问结构的方法 reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.ValueOf(1))
Struct Tag 1 2 3 4 type BasicInfo struct { Name string `json:"name"` Age int `json:"age"` }
string 与 int 后面的结构就是 Struct Tag 用法
struct tag的可选字段Tag可选的字段:
"-" :不要解析这个字段
"omitempty":当字段为空(默认值)时,不要解析这个字段;比如是false、0、nil或者长度为0的array、map、slice、string等
FieldName:当解析json、xml、ini等的时候 用这个名字
访问Struct Tag 1 2 3 4 5 6 if nameField, ok := reflect.TypeOf(*r).FiledName("Name"); !ok { t.Error("Failed to get 'Name' field.") } else { t.Log("Tag:format", nameField.Tag.Get("format")) }
Reflect.Type和Reflect.Value都有FieldByName方法,注意区分
DeepEqual 比较Map和切片 1 2 3 4 5 6 7 8 9 10 11 12 func TestDeepEqual(t *testing.T) { a := map[int]string{1: "one", 2: "two", 3: "three"} b := map[int]string{1: "one", 2: "two", 4: "three"} //t.Log(a == b) t.Log(reflect.DeepEqual(a, b)) s1 := []int{1, 2, 3} s2 := []int{1, 2, 3} s3 := []int{2, 3, 1} t.Log("s1 == s2?", reflect.DeepEqual(s1, s2)) t.Log("s1 == s3?", reflect.DeepEqual(s1, s3)) }
万能程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 type Employee1 struct { EmployeeID string Name string `format:"normal"` Age int } func (e *Employee1) UpdateAge(newVal int) { e.Age = newVal } type Customer1 struct { CookieID string Name string Age int } func fillBySettings(st interface{}, settings map[string]interface{}) error { if reflect.TypeOf(st).Kind() != reflect.Ptr { // Elem() 获取指针指向的值 if reflect.TypeOf(st).Elem().Kind() != reflect.Struct { return errors.New("the first param should be a pointer to struct type") } } if settings == nil { return errors.New("settings is nil") } var ( field reflect.StructField ok bool ) for k, v := range settings { if field, ok = (reflect.ValueOf(st)).Elem().Type().FieldByName(k); !ok { continue } fmt.Println(k, reflect.TypeOf(k), field, field.Type, v, reflect.TypeOf(v)) if field.Type == reflect.TypeOf(v) { vstr := reflect.ValueOf(st) vstr.Elem().FieldByName(k).Set(reflect.ValueOf(v)) } } return nil } func TestFillNameAndAge(t *testing.T) { settings := map[string]interface{}{"Name": "Mike", "Age": 40} e := Employee1{} if err := fillBySettings(e, settings); err != nil { t.Fatal(err) } t.Log(e) c := new(Customer1) if err := fillBySettings(c, settings); err != nil { t.Fatal(err) } t.Log(*c) }
反射必要了解
Go语言反射的实现原理:https://mp.weixin.qq.com/s?__biz=MzU5NTAzNjc3Mg==&mid=2247483962&idx=1&sn=e13df5c5e016215302205f5ec8fbb857&scene=21#wechat_redirect
不安全编程
使用C高效库
不安全行为的危险性不合理的类型转换(强制类型转换) 1 2 3 4 5 6 func TestUnsafe(t *testing.T) { i := 10 f := *(*float64)(unsafe.Pointer(&i)) t.Log(unsafe.Pointer(&i)) t.Log(f) }
合理的类型转换(别名) 1 2 3 4 5 6 7 type MyInt int func TestConvert(t *testing.T) { a := []int{1, 2, 3, 4} b := *(*[]MyInt)(unsafe.Pointer(&a)) t.Log(b) }
原子类型的操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func TestAtomic(t *testing.T) { var shareBufPtr unsafe.Pointer writeDataFn := func() { data := []int{} for i := 0; i < 100; i++ { data = append(data, i) } atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data)) } readDataFm := func() { data := atomic.LoadPointer(&shareBufPtr) fmt.Println(data, *(*[]int)(data)) } var wg sync.WaitGroup writeDataFn() for i := 0; i < 10; i++ { wg.Add(1) go func() { for i := 0; i < 10; i++ { writeDataFn() time.Sleep(time.Millisecond * 100) } wg.Done() }() wg.Add(1) go func() { for i := 0; i < 10; i++ { readDataFm() time.Sleep(time.Millisecond * 100) } wg.Done() }() } wg.Wait() }
channel版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func TestChainAtomic(t *testing.T) { //shareBufPtr := make(chan unsafe.Pointer) shareBufPtr := make(chan unsafe.Pointer, 1) //var shareBufPtr unsafe.Pointer writeDataFn := func() { var shareBufPointer unsafe.Pointer data := []int{} for i := 0; i < 100; i++ { data = append(data, i) } atomic.StorePointer(&shareBufPointer, unsafe.Pointer(&data)) shareBufPtr <- shareBufPointer //t.Log(shareBufPtr) } readDataFm := func() { shareBufPointer := <-shareBufPtr //t.Log(shareBufPointer) data := atomic.LoadPointer(&shareBufPointer) fmt.Println(data, *(*[]int)(data)) } var wg sync.WaitGroup writeDataFn() for i := 0; i < 10; i++ { wg.Add(1) go func() { for i := 0; i < 10; i++ { writeDataFn() time.Sleep(time.Millisecond * 100) } wg.Done() }() wg.Add(1) go func() { for i := 0; i < 10; i++ { readDataFm() time.Sleep(time.Millisecond * 100) } wg.Done() }() } wg.Wait() }
软件架构 Pipe-Filter 架构
数据处理、数据分析
非常适合与数据处理及数据分析系统
Filter 封装数据处理的功能
松耦合:Filter 只跟数据(格式)耦合
Pipe 用于连接Filter传递数据或者异步处理过程中缓冲数据流进程内同步调用时,pipe演变为数据在方法调用间传递
==测试源码 ==
实现micro kernel framework 特点
要点
内核包含公共流程或通用逻辑
将可变或可扩展部分规划为扩展点
抽象扩展点行为,定义接口
利用插件进行扩展
Example
模拟源码
常用任务/工具 JSON解析内置JSON解析
利用反射实现,通过FieldTag来表示对应的json值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 type BasicInfo struct { Name string `json:"name"` Age int `jsom:"age"` } type JobInfo struct { Skills []string `json:"skills"` } type Employee struct { BasicInfo BasicInfo `json:"basic_info"` JobInfo JobInfo `json:"job_info"` } var jsonStr = ` { "basic_info": { "name": "Mike", "age": 30 }, "job_info": { "skills": ["Java", "Go", "C"] } } ` func TestEmbeddedJson(t *testing.T) { e := new(Employee) err := json.Unmarshal([]byte(jsonStr), e) if err != nil { t.Error(err) } fmt.Println(*e) if v, err := json.Marshal(e); err != nil { t.Error(err) } else { fmt.Println(string(v)) } }
EasyJSON
EasyJSON采用代码生成而非反射
安装 go get -u github.com/mailru/easyjson/...
使用 easyjson -all <结构定义>.go
参考源码:https://note.youdao.com/s/RrFLzfgY
HTTP服务路由规则
URL分为两种,末尾是/: 表示一个子树,后面可以跟其他子路径;末尾不是/,表示一个叶子,固定的路径
以/结尾的URL可以匹配它的任何子路径,比如/images会匹配/images/cute-cat.jpg
它采用最长匹配机制,如果有多个匹配,一定采用匹配路径最长的那个进行处理
如果没有找到任何匹配项,会返回404错误
1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello world!") }) http.HandleFunc("/time/", func(writer http.ResponseWriter, request *http.Request) { t := time.Now() timeStr := fmt.Sprintf("{\"time\": \"%s\"}", t) writer.Write([]byte(timeStr)) }) http.ListenAndServe(":8080", nil) }
构建RESTful服务 更好的Router
https://github.com/julienschmidt/httprouter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import ( "fmt" "github.com/julienschmidt/httprouter" "log" "net/http" ) func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprintf(w, "Welcome!\n") } func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) } func main() { router := httprouter.New() router.GET("/", Index) router.GET("/hello/:name", Hello) log.Fatal(http.ListenAndServe(":8080", router)) }
面向资源的架构(Resource Oriented Architecture)
在软件工程中,面向资源的架构(ROA)是一种软件架构风格和编程范式,用于支持设计和开发具有 “RESTful “接口的资源互联网形式的软件。这些资源是软件组件(离散的代码和/或数据结构),可以为不同的目的重复使用。ROA的设计原则和指导方针在软件开发和系统集成阶段被使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import ( "encoding/json" "fmt" "github.com/julienschmidt/httprouter" "log" "net/http" ) type Employee struct { ID string `json:"id"` Name string `json:"name"` Age int `json:"age"` } var employeeDB map[string]*Employee func init() { employeeDB = map[string]*Employee{} employeeDB["Mike"] = &Employee{"e-1", "Mike", 35} employeeDB["Rose"] = &Employee{"e-1", "Rose", 45} } func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprintf(w, "Welcome!\n") } func GetEmployeeByName(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { qName := ps.ByName("name") var ( ok bool info *Employee infoJson []byte err error ) if info, ok = employeeDB[qName]; !ok { w.Write([]byte("{\"error\": \"Not Found\"}")) return } if infoJson, err = json.Marshal(info); err != nil { w.Write([]byte(fmt.Sprintf("{\"error\": \"%s\"}", err))) return } w.Write(infoJson) } func main() { router := httprouter.New() router.GET("/", Index) router.GET("/employee/:name", GetEmployeeByName) fmt.Println("http://127.0.0.1:8080") log.Fatal(http.ListenAndServe(":8080", router)) }
性能分析与性能调优 pprof指南
https://zhuanlan.zhihu.com/p/396363069
性能分析工具
安装graphviz
将$GOPATH/bin加入$PATH
安装go-torch
go get github.com/uber/go-torch
通过文件方式输出Profile
灵活性高,适用于特定代码段的分析
通过手动调用runtime/pprof的API
API相关文档
go tool pprof [binary] [binary.prof]
Go支持的多种Profile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package main import ( "log" "math/rand" "os" "runtime/pprof" "time" ) const ( col = 10000 row = 10000 ) func fillMatrix(m *[row][col]int) { r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < row; i++ { for j := 0; j < col; j++ { m[i][j] = r.Intn(100000) } } } func calculate(m *[row][col]int) { for i := 0; i < row; i++ { tmp := 0 for j := 0; j < col; j++ { tmp += m[i][j] } } } func main() { // 创建输出文件 f, err := os.Create("cpu.prof") if err != nil { log.Fatal("could not create CPU profile: ", err) } if err := pprof.StartCPUProfile(f); err != nil { // 监控CPU log.Fatal("could not start CPU profile: ", err) } defer pprof.StopCPUProfile() // 主逻辑区,进行一些简单的代码运算 x := [row][col]int{} fillMatrix(&x) calculate(&x) f1, err := os.Create("mem.prof") if err != nil { log.Fatal("could not create memory profile: ", err) } //runtime.GC() // GC, 获取最新的数据信息 if err := pprof.WriteHeapProfile(f1); err != nil { // 写入内存消息 log.Fatal("could not write memory profile: ", err) } f1.Close() f2, err := os.Create("goroutine.prof") if err != nil { log.Fatal("could not create groutine profile: ", err) } if gProf := pprof.Lookup("goroutine"); gProf != nil { gProf.WriteTo(f2, 0) } else { log.Fatal("could not write groutine profile: ") } f2.Close() }
生成.prof文件 go build prof.go
测试它 go tool pprof [prof|pprof.exe] *.prof
通过HTTP方式输出Profile
简单,适合于持续性运行的应用
在应用程序中导入import _ "net/http/pprof", 并启动http server即可
http://<host>:<posrt>/debug/pprof/
go tool pprof _http://<host>:<port>/debug/pprof/profile?seconds=10(默认值为30秒)
go-tourch -seconds 10 http://<host>:<port>/debug/pprof/profile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package main import ( "fmt" "log" "net/http" _ "net/http/pprof" ) func GetFibonacciSerie(n int) []int { ret := make([]int, 2, n) ret[0] = 1 ret[1] = 1 for i := 2; i < n; i++ { ret = append(ret, ret[i-2]+ret[i-1]) } return ret } func index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("welcome!")) } func createFBS(w http.ResponseWriter, r *http.Request) { var fbs []int for i := 0; i < 1000000; i++ { fbs = GetFibonacciSerie(50) } w.Write([]byte(fmt.Sprintf("%v", fbs))) } func main() { http.HandleFunc("/", index) http.HandleFunc("/fb", createFBS) log.Fatal(http.ListenAndServe(":8081", nil)) }
然后 go tool pprof -http=":8082" http://192.168.0.103:8081/debug/pprof/profile?seconds=10
性能调优
常见分析指标
Wall Time: 测量的是过去了多少时间
CPU Time
CPU时间是使用中央处理单元处理计算机程序或操作系统的指令的时间量,与经过的时间相对应,包括等待输入/输出操作或进入低功耗模式的时间。
Block Time: 运行阻塞的时间
Memory allocation: 动态内存分配又称为堆内存分配,是指计算机程序在运行期中分配使用内存
GC times/time spent: 自上次GC结束后在GC中花费的时间
自上次GC结束后在GC中花费的时间的百分比。例如,从上一次GC结束到现在已经有100万个周期,我们在当前的GC中花费了30万个周期,这个计数器将显示30%。
Go bench生成prof
go test -bench=. -cpuprofile=cpu.profgo test -bench=. -memprofile=cem.prof
Example structs.go
1 2 3 4 5 6 7 8 9 10 11 12 package memOperation type Request struct { TransactionID string `json:"transaction_id"` Payload []int `json:"payload"` } type Response struct { TransactionID string `json:"transaction_id"` Expression string `json:"expression"` }
optimization.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package memOperation import ( "encoding/json" "strconv" "strings" ) func createRequest() string { payload := make([]int, 100, 100) for i := 0; i < 100; i++ { payload[i] = i } req := Request{"demo_transaction", payload} v, err := json.Marshal(&req) if err != nil { panic(err) } return string(v) } func processRequestOld(reqs []string) []string { reps := []string{} for _, req := range reqs { reqObj := &Request{} json.Unmarshal([]byte(req), reqObj) ret := "" for _, e := range reqObj.Payload { ret += strconv.Itoa(e) + "," } repObj := &Response{reqObj.TransactionID, ret} repJson, err := json.Marshal(&repObj) if err != nil { panic(err) } reps = append(reps, string(repJson)) } return reps } func processRequest(reqs []string) []string { reps := []string{} for _, req := range reqs { reqObj := &Request{} reqObj.UnmarshalJSON([]byte(req)) //json.Unmarshal([]byte(req), reqObj) ret := "" var buf strings.Builder for _, e := range reqObj.Payload { buf.WriteString(strconv.Itoa(e)) buf.WriteString(",") //ret += strconv.Itoa(e) + "," } repObj := &Response{reqObj.TransactionID, ret} repJson, err := repObj.MarshalJSON() //repJson, err := json.Marshal(&repObj) if err != nil { panic(err) } reps = append(reps, string(repJson)) } return reps }
optimization_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package memOperation import "testing" func TestCreateRequest(t *testing.T) { str := createRequest() t.Log(str) } func TestProcessRequest(t *testing.T) { reqs := []string{} reqs = append(reqs, createRequest()) reps := processRequest(reqs) t.Log(reps[0]) } func BenchmarkProcessRequest(b *testing.B) { reqs := []string{} reqs = append(reqs, createRequest()) b.ResetTimer() for i := 0; i < b.N; i++ { _ = processRequest(reqs) } b.StopTimer() } func BenchmarkProcessRequestOld(b *testing.B) { reqs := []string{} reqs = append(reqs, createRequest()) b.ResetTimer() for i := 0; i < b.N; i++ { _ = processRequestOld(reqs) } b.StopTimer() }
使用easyjson优化Json解析的问题 easyjson.exe -all structs.go
性能不能被锁住 锁对性能的影响比较大 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package notBeLock import ( "fmt" "sync" "testing" ) var cache map[string]string const NUM_OF_READER int = 40 const READ_TIMES int = 100000 func init() { cache = make(map[string]string) cache["a"] = "aa" cache["b"] = "bb" } func lockFreeAccess() { var wg sync.WaitGroup wg.Add(NUM_OF_READER) for i := 0; i < NUM_OF_READER; i++ { go func() { for j := 0; j < READ_TIMES; j++ { _, err := cache["a"] if !err { fmt.Println("Noting") } } wg.Done() }() } wg.Wait() } func lockAccess() { var wg sync.WaitGroup wg.Add(NUM_OF_READER) m := new(sync.RWMutex) for i := 0; i < NUM_OF_READER; i++ { go func() { for j := 0; j < READ_TIMES; j++ { m.RLock() _, err := cache["a"] if !err { fmt.Println("Noting") } m.RUnlock() } wg.Done() }() } wg.Wait() } func BenchmarkLockFree(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { lockFreeAccess() } b.StopTimer() } func BenchmarkLock(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { lockAccess() } b.StopTimer() }
sync.Map,原Map不支持线程安全
性能锁小结
减少锁的影响范围
减少发生锁冲突的概率
避免锁的使用
Example
https://note.youdao.com/s/5KLTdMdd
GC友好代码避免内存分配和复制
打开GC日志 只要在程序之前加上环境变量GODEBUG=gctrace=1, 如:GODEBUG=gctrace=1 go test -bench=.GODEBUG=gctrace=1 go run main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package passing_ref import ( "testing" ) const NumOfElems = 1000 type Content struct { Detail [10000]int } func withValue(arr [NumOfElems]Content) int { //fmt.Println(&arr[2]) return 0 } func withReference(arr *[NumOfElems]Content) int { //b := *arr //for i, content := range b { // fmt.Println(i) // fmt.Println(content) //} //fmt.Println(&arr[2]) return 0 } func TestIn(t *testing.T) { var arr [NumOfElems]Content withValue(arr) withReference(&arr) } func BenchmarkPassingArrayWithValue(b *testing.B) { var arr [NumOfElems]Content b.ResetTimer() for i := 0; i < b.N; i++ { withValue(arr) } b.StopTimer() } func BenchmarkPassingArrayWithRef(b *testing.B) { var arr [NumOfElems]Content b.ResetTimer() for i := 0; i < b.N; i++ { withReference(&arr) } }
普通程序输出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "os" "runtime/trace" ) func main() { f, err := os.Create("trace.out") if err != nil { panic(err) } defer f.Close() err = trace.Start(f) if err != nil { panic(err) } defer trace.Stop() // Your program here }
测试程序输出trace信息 go test -trace trace.out
可视化trace信息 go tool trace trace.out
避免内存分配和复制
高效字符串连接
推荐使用strings.Builder(Go 1.10后版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package concatString import ( "bytes" "fmt" "strconv" "strings" "testing" ) const numbers = 100 func BenchmarkSprintf(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var s string for i := 0; i < numbers; i++ { s = fmt.Sprintf("%v%v", s, i) } } b.StopTimer() } func BenchmarkStringBuilder(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var builder strings.Builder for i := 0; i < numbers; i++ { builder.WriteString(strconv.Itoa(i)) } _ = builder.String() } b.StopTimer() } func BenchmarkBytesBuf(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var buf bytes.Buffer for i := 0; i < numbers; i++ { buf.WriteString(strconv.Itoa(i)) } _ = buf.String() } b.StopTimer() } func BenchmarkStringAdd(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var s string for i := 0; i < numbers; i++ { s += strconv.Itoa(i) } } b.StopTimer() }
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 goos: windows goarch: amd64 pkg: stringOperatation/concatString cpu: AMD Ryzen 7 3700X 8-Core Processor BenchmarkSprintf BenchmarkSprintf-16 72243 16357 ns/op BenchmarkStringBuilder BenchmarkStringBuilder-16 1815912 660.3 ns/op BenchmarkBytesBuf BenchmarkBytesBuf-16 1448607 832.6 ns/op BenchmarkStringAdd BenchmarkStringAdd-16 249799 4913 ns/op PASS
分布式系统架构(高可用) 面向错误设计
隔离
当系统一部分发生错误时候,尽量减少它对其他部分的影响,让系统能按一定功能继续工作,可以被用户所使用
微内核
微服务
重用 vs 隔离
冗余
单点失效
解决单点失效—— 限流
扛不住就只能增加服务器了
慢响应
不要无休止的等待
错误传递
断路器
面向恢复设计
健康检查
Let it Crash
构建可恢复的系统
与客户端协商
chaos engineering
如果问题经常发生人们就会学习和思考解决它的办法
Cgaos Engineering原则
Build a Hypothesis around Steady State Behavior
围绕稳定行为建立一个假设
Vary Real-World Events
尝试各种各样真实世界的事件
Run Experiments in Production
实验运行在生产环境中
Automate Experiments to Run Continuously
自动化实验持续运行而不是依赖人工
Minimize Blast Radius
最小化毁坏范围,不能给用户带来影响
http://principlesofchaos.org
相关开源项目 https://github.com/Netflix/chaosmonkey
https://github.com/easierway/service_decorators/blob/master/README.md
图书推荐