源码地址: https://gitee.com/geektime-geekbang/go_learning
or https://github.com/geektime-geekbang/go_learning/

Go 语言从入门到实战 笔记#

基础部分#

差异与基础#

退出返回值#

获取命令行参数#

变量赋值#

快速设置连续值#

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
)

类型转化#

  1. Go语言不允许隐式类型转换
  2. 别名和原有类型也不能进行隐式类型转换

指针类型#

  1. 不支持指针运算
  2. string是值类型,其默认得初始化值为空字符串,而不是nil

算术运算符#

Go语言没用前置得++,–, (++a)

==比较数组#

位运算符,&^按位置零#

1 &^ 0 -- 1
1 &^ 1 -- 0
0 &^ 1 -- 0
0 &^ 0 -- 0

循环#

while 条件循环
while (n<5)

1
2
3
4
5
n := 0
for n < 5{
n++
fmt.Println(n)
}

无限循环
while(true)

1
2
3
4
n := 0
for {
...
}

if条件#

  1. condition表达式结果必须为布尔值
  2. 支持变量赋值:
    1
    2
    3
    if var declaration; condition {
    // code to be executed if confition is true
    }

switch条件#

  1. 条件表达式不限制为常量或者整数;
  2. 单个case中,可以出现多个结果选项,是用逗号分隔;
  3. 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
  4. 可以不设定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个元素会被初始化为默认零值,未初始化元素不可以访问
*/

image.png

切片如何实现可变长#

1
s=append(s,i) // 地址发生变化

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与工厂模式#

实现Set#

Go的内置集合中没有Set实现,可以map[type]bool

  1. ==元素的唯一性==
  2. 基本操作
    1. 添加元素
    2. 判断元素是否存在
    3. 删除元素
    4. 元素个数
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)
//t.Log(len(mySet))
n = 1
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n)
}

字符串#

  1. string是数据类型,不是引用或指针类型
  2. string是只读的byte slice, len函数可以查它所包含的byte
  3. string的byte数组可以存放任何数据

Unicode UTF8#

  1. Unicode 是一种字符集(code point)
  2. UTF8 是 unicode 的存储实现(转换为字节系列的规则)
    image.png

String转rune#

1
2
3
4
s := "去隔壁吃辣条"
for _, c := range s {
t.Logf("%[1]c %[1]x %[1]d %[2]x", c, string(c)) // "[]"中的数字代表抵
}

常用的字符串函数#

  1. strings包
  2. 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)
}

函数是一等公民#

  1. 可以有多个返回值
  2. 所有参数都是值传递:slice, map, channel会有传引用的错觉
  3. 函数可以作为变量的值
  4. 函数可以作为参数和返回值

可变参数#

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的错误机制#

  1. 没有异常机制
  2. error 类型实现了 error接口

    及早失败,避免嵌套!

    1
    2
    3
    type error interface {
    Error() string
    }
  3. 可以通过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 VS os.Exit#

==console:==

1
2
3
=== RUN   TestPanicVxExit
Start

没打印Finally

捕获所有错误 recover#

image.png

GO语言中recover

1
2
3
4
5
defer func() {
if err := recover(); err != nil {
// 恢复错误
}
}()

recover()返回的是panicerror例如:panic(error.New("错误信息"))

==使用recover需谨慎==#

GO的依赖管理#

package#

  1. 基本复用模块单元

    以首字母大写来表明可被包外代码访问

  2. 代码的package可以和所在的目录不一致
  3. 同一目录里的Go代码的package要保持一致

remote package#

  1. 通过get get来获取远程依赖
  1. 注意代码在Github上的组织形式,以适应go get

init方法#

init函数的主要作用:#

init函数的主要特点:#

初始化顺序:变量初始化->init()->main()

Go未解决的依赖问题#

  1. 同一环境下,不同项目使用同一包的不同版本
  2. 无法管理对包的特定版本的依赖

vendor路径#

查找依赖包管理的解决方法如下

  1. 当前包下的vendor目录
  2. 向上级目录查找,直到找到src下的vendor目录
  3. GOPATH下面查找依赖包
  4. 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#

  1. 创建时默认的stack的大小
  1. KSE(Kernel Space Entity)的对应关系

共享内存并发机制#

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中的join#

1
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 是一种形式语言,用来描述并发性系统间进行交互的模式。它是叫做进程代数或进程演算的关于并发的数学理论家族的一员,基于了通过通道的消息传递

CSP vs Actor#

Channel#

image.psd.png

image.png

多路选择和超时#

多路选择#

如果多个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的关闭#

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 与 任务取消#

image.png

Context#

ctx, cancel := context.WithCancel(context.Background())

cancel是一个方法

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)
}

只运行一次#

单例模式 (懒汉式,线程安全)#

仅需任意任务完成#

利用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())
}

对象池#

image.png

使用不同的池缓冲不同的对象
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对象获取#

sync.Pool对象的返回#

使用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对象的生命周期#

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接口与其他主流编程语言的差异#

  1. 接口为非入侵性,实现不依赖于接口定义
  2. 所以接口的定义可以包含在接口使用者的包内(不会产生循环依赖)
    image.psd.png

自定义类型#

  1. type IntCovertionFn func(n int) int
  2. type MyPoint int
    1
    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)
}

空接口与断言#

  1. 空接口可以表示==任何类型==
  2. 通过断言来将空接口转换为制定类型
    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")
    }

image.psd.png

测试#

测试testing#

  1. 源码文件以_test结尾:xxx_test.go
  2. 测试文件名以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

BDD#

Behavior Driven Development
image.psd.png

项目网站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#

判断类型-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可选的字段:

访问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.TypeReflect.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 架构#

数据处理、数据分析
image.png

image.png

image.png

==测试源码==

实现micro kernel framework#

特点

要点

Example
image.png

模拟源码

常用任务/工具#

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服务#

路由规则#

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

性能分析工具#

通过文件方式输出Profile#

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#

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

性能调优#

image.png

常见分析指标#

Go bench生成prof

go test -bench=. -cpuprofile=cpu.prof
go 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不支持线程安全#

image.png

性能锁小结#

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
image.png

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)
}
}

go tool trace#

普通程序输出#
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

分布式系统架构(高可用)#

面向错误设计#

image.png

隔离#

当系统一部分发生错误时候,尽量减少它对其他部分的影响,让系统能按一定功能继续工作,可以被用户所使用

微内核#

image.png

微服务#

image.png

重用 vs 隔离#

image.png

冗余#

image.png

单点失效#

image.png

解决单点失效—— 限流#

扛不住就只能增加服务器了

image.png

慢响应#

image.png

不要无休止的等待#

image.png

错误传递#

image.png

断路器#

image.png

面向恢复设计#

image.png

健康检查#

Let it Crash#

image.png

构建可恢复的系统#

与客户端协商#

image.png

chaos engineering#

image.png

Cgaos Engineering原则#

http://principlesofchaos.org

相关开源项目#

https://github.com/Netflix/chaosmonkey

https://github.com/easierway/service_decorators/blob/master/README.md

图书推荐#