golang学习笔记

young 528 2022-05-28

下载安装Go语言

https://golang.org/doc/install

https://golang.google.cn/dl/

安装IDE

Atom:https://atom.io + Package:go-plus

开发环境构建

GOPATH

  1. 1.8版本之前必须设置这个环境变量

  2. 1.8版本之后(含1.8),如果没有设置使用默认值

    在Unix上默认为$HOME/go,在Windows上,默认为%USERPROFILE%/go

    Mac上GOPATH可以通过修改~./bash_profile来设置

  3. 可以通过go version查看go语言的版本

用命令行运行go程序

go run hello_world.go

编译go程序

go build hello_world.go

基本程序结构

package main // 包,表明代码所在的模块

import "fmt" // 引入代码依赖

// 功能实现
func main() {
   fmt.Println("Hello world")
}

package和路径名不一定要求保持一致

应用程序入口

  1. 必须是main包:package main
  2. 必须是main方法: func main()
  3. 文件名不一定是main.go

退出返回值

与其他只要编程语言的差异

  • Go中main函数不支持任何返回值
  • 通过os.Exit来返回状态
func main() {
   fmt.Println("Hello world")
   os.Exit(0)
}

获取命令行参数

与其他主要编程语言的差异

  • main函数不支持传入参数

    func main(arg[] String)

  • 在程序中直接通过os.Args获取命令行参数

func main() {
   fmt.Println(os.Args)
   fmt.Println("Hello world")
   os.Exit(0)
}
go run hello_world.go young
[/var/folders/k2/n1jx0n1s7p5f_x2d3gpyzgcr0000gn/T/go-build1414244723/b001/exe/hello_world young]
Hello world
func main() {
   if len(os.Args)>1 {
      fmt.Println("Hello world",os.Args[1])
   }
}
go run hello_world.go young
Hello world young

编写测试程序

  1. 源码文件以_test结尾:xxx_test.go
  2. 测试方法名以Test开头:func TestXXX(t *testing.T)

创建文件first_test.go

package try_test

import "testing"

func TestFirstTry(t *testing.T) {
   t.Logf("My first try!")
}

控制台输出

go tool test2json -t /private/var/folders/k2/n1jx0n1s7p5f_x2d3gpyzgcr0000gn/T/GoLand/___TestFirstTry_in_first_test_go.test -test.v -test.paniconexit0 -test.run ^\QTestFirstTry\E$
=== RUN   TestFirstTry
    first_test.go:6: My first try!
--- PASS: TestFirstTry (0.00s)
PASS

实现Fibonacci数列

1,1,2,3,5,8,13……

package fib

import (
   "fmt"
   "testing"
)

func TestFibList(t *testing.T) {
   // 用var声明变量,如果声明的变量未使用,是报错
   //var a int = 1
   //var b int =
   // 可以一次声明多个变量,go语言有一定的类型推断能力,可以省略一部分类型声明
   //var (
   // a int = 1
   // b     = 1
   //)
   a := 1
   b := 1
   t.Log(a)
   for i := 0; i < 5; i++ {
      t.Log(" ", b)
      tmp := a
      a = b
      b = tmp + a
   }
}

变量赋值

与其他主要编程语言的差异

  • 赋值可以进行自动类型推断
  • 在一个赋值语句中可以对多个变量进行同时赋值
// 交换两个变量的值
func TestChange(t *testing.T) {
   a := 1
   b := 2
   //tmp := a
   //a = b
   //b = tmp
   a, b = b, a
   t.Log(a, b)
}

常量定义

与其他主要编程语言的差异

快速连续赋值

const (
  Monday = iota+1
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
  Sunday
)

const(
  Open = 1 << iota
  Close
  Pending
)
const (
   Monday = iota + 1
   Tuesday
   Wednesday
   Thursday
   Friday
   Saturday
   Sunday
)

const (
   Open = 1 << iota
   Close
   Pending
)

func TestConstantTry(t *testing.T) {
   t.Log(Monday, Tuesday)
}

func TestConstantTry1(t *testing.T) {
   a := 7 //0111
   t.Log(a&Open == Open, a&Close == Close, a&Pending == Pending)
}

基本数据类型

类型
bool
string
int int8 int16 int32 int64
uint uint8 unint16 unint32 uint64 uintptr
byte // alias for uint8
rune // alias for int 32,represents a Unicode code point
float32 float64
complex64 complex128

int和uint在32为机上是32位,在64位机上是64位

类型转化

与其他主要编程语言的差异

  1. Go语言不允许隐式类型转换
  2. 别名和原有类型越不能进行隐式类型转换
func TestImplicit(t *testing.T) {
   var a int = 1
   var b int64
   b = a
   t.Log(a, b)
}
// cannot use a (variable of type int) as type int64 in assignment
// 显式类型转换可以进行
func TestImplicit(t *testing.T) {
   var a int = 1
   var b int64
   b = int64(a)
   t.Log(a, b)
}
// 声明类型别名
// 别名也不能进行隐式类型转换
type MyInt int64

func TestImplicit(t *testing.T) {
   var a int = 1
   var b int64
   b = int64(a)
   var c MyInt
   c = b
   t.Log(a, b)
}

类型的预定义值

  1. math.MaxInt64
  2. math.MaxFloat64
  3. math.MaxUint32

指针类型

与其他主要编程语言的差异

  1. 不支持指针运算
  2. string是值类型,其默认的初始化值为空字符串,而不是nil
func TestPoint(t *testing.T) {
   a := 1
   aPtr := &a
   t.Log(a, aPtr)
   t.Logf("%T %T", a, aPtr)
}
func TestPoint(t *testing.T) {
   a := 1
   aPtr := &a
   aPtr = aPtr+1 // 异常,不支持指针运算
   t.Log(a, aPtr)
   t.Logf("%T %T", a, aPtr)
}
func TestString(t *testing.T) {
   var s string
   t.Log("*"+s+"*") // 输出**
  t.log(len(s)) // 0
}

算术运算符

运算符 描述 实例
+ 相加 A+B输出结果30
- 相减 A-B输出结果-10
* 相乘 A*B输出结果200
/ 相除 B/A输出结果2
% 求余 B%A输出结果0
++ 自增 A++输出结果11
自减 A–输出结果9

Go语言没有前置的++,——

比较运算符

大部分与主流语言没什么差别

用==比较数组

  • 相同维数且含有相同个数元素的数组才可以比较
  • 每个元素都相同的才相等
unc TestCompareArray(t *testing.T) {
   a:=[...] int {1,2,3,4}
   b:=[...] int {1,2,3,5}
   c:=[...] int {1,2,3,4,5}
   d:=[...] int {1,2,3,4}
   t.Log(a==b)
   // t.Log(a==c) 长度不同,编译报错
   t.Log(a==d)
}

位运算符

与其他主要编程语言的区别

&^ 按位清零

只要右边的二进制为1,就会把左边的二进制位清零,右边的二进制为0,会保留左边的二进制位

1 &^ 0 – 1

1 &^ 1 – 0

0 &^ 1 – 0

0 &^ 0 – 0

const (
   Readable = 1 << iota
   Writeable
   Executable
)

func TestBitClear(t *testing.T) {
	a := 7 //0111
	a = a &^ Readable
	t.Log(a&Readable == Readable, a&Writeable == Writeable, a&Executable == Executable)
}

循环

与其他主要编程语言差异

Go语言仅支持循环关键字for

for (j:=7;j<=9;j++)

不需要括号

// while条件循环
// while (n<5)
func TestWhileLoop(t *testing.T) {
	n := 0
	for n < 5 {
		t.Log(n)
		n++
	}
}

// 无限循环
// while true
n:=0
for {
  ...
}

条件语句

if condition {
  // code to be executed if condition is true
} else {
 // code to be executed if condition is false
}
if condition-1 {
  // code to be executed if condition-1 is true
} else if condition-2 {
    // code to be executed if condition2 is true
} else {
  // code to be executed if both condition-1 and condition-2 are false
}

与其他主要编程语言的差异

  1. Condition 表达式结果必须为布尔值
  2. 支持变量赋值
if var declaration ;condition {
  // code to be excuted if condition is true
}
func TestIfMultiSec(t *testing.T) {
   if a := 1 == 1; a {
      t.Logf("1==1")
   }
}
func TestIfMultiSec(t *testing.T) {
   // go语言方法支持多返回值
  // 第一个变量是方法本身的返回值,第二个变量是方法返回的错误,判断错误如果为空(m没有错误)
   if v, err := someFun(); err == nil {
      t.Logf("1==1")
   } else {
      t.Log("1==1")
   }
}

switch条件

switch os := runtime.GOOS; os {
  case "darwin":
    fmt.Println("OS X")
  	// break
  case "linux":
    fmt.Println("linux")
  default:
    // freebsd, openbsd
    // plan9 ,windows...
    fmt.Println("%s",os)
}
switch {
  case 0 <=Num&& Num <=3:
  	fmt.Println("0-3")
  case 4<= Num && Num <=6:
  	fmt.Println("4-6")
  case 7<= Num && Num <=9:
  	fmt.Println("7-9")
}

与其他主要编程语言的差异

  1. 条件表达式不限制为常量或者整数
  2. 单个case中,可以出现多个结果选项,使用逗号分隔
  3. Go语言不需要使用break来明确退出一个case
  4. 可以不设定switch之后的条件表达式,在这种情况下,整个switch结构与多个if…else…的逻辑作用等同
func TestSwitchMultiCase(t *testing.T) {
   for i := 0; i < 5; i++ {
      switch i {
      case 0, 2:
         t.Log("Even")
      case 1, 3:
         t.Log("Odd")
      default:
         t.Log("it is not 0-3")
      }
   }
}
unc TestSwitchCaseCondition(t *testing.T) {
   for i := 0; i < 5; i++ {
      switch {
      case i%2 == 0:
         t.Log("Even")
      case i%2 == 1:
         t.Log("Odd")
      default:
         t.Log("it is not 0-3")
      }
   }
}

数组的声明

var a [3]int // 声明并初始化为默认零值
a[0] = 1
b := [3]int{1,2,3} // 声明同时初始化
c := [2][2]int{1,2}{3,4} // 多维数组初始化
func TestArrayInt(t *testing.T) {
   var arr [3]int
   arr1 := [4]int{1, 2, 3, 4}
   arr3 := [...]int{1, 3, 4, 5} // 不指定元素个数,自动确定数组长度
   arr1[1] = 5
   t.Log(arr[1], arr[2])
   t.Log(arr1, arr3)
}
// 数组遍历
func TestArrayTravel(t *testing.T) {
   arr3 := [...]int{1, 3, 4, 5}
   for i := 0; i < len(arr3); i++ {
      t.Log(arr3[i])
   }

   // idx是索引的值,e是元素的值
   for idx, e := range arr3 {
      t.Log(idx, e)
   }

   // 不需要索引值的时候,可以使用下划线_进行占位
   for _, e := range arr3 {
      t.Log(e)
   }
}

数组截取

a[开始索引(包含),结束索引(不包含)]
a := [...]int{1,2,3,4,5}
a[1:2] // 2
a[1:3] // 2,3
a[1,len(a)] // 2,3,4,5
a[1:] // 2,3,4,5
a[:3]// 1,2,3
a[:]
func TestArraySection(t *testing.T) {
   arr3 := [...]int{1, 2, 3, 4, 5}
   arr3_sec := arr3[3:]
   t.Log(arr3_sec)
}

切片内部结构

切片内部结构

ptr指针:指向一个连续的存储空间(数组)

len:元素的个数

cap:数组空间的长度

func TestSliceInit(t *testing.T) {
   // 不用指定数组长度,是可变长的
   var s0 []int
   // 打印len长度和cap长度
   t.Log(len(s0), cap(s0))
   // 添加元素
   s0 = append(s0, 1)
   t.Log(len(s0), cap(s0))
   // 初始化
   s1 := []int{1, 2, 3, 4}
   t.Log(len(s1), cap(s1))

   s2 := make([]int, 3, 5)
   t.Log(len(s2), cap(s2))
   // index out of range [3]
   //t.Log(s2[0], s2[1], s2[2], s2[3], s2[4])
   // 只初始化了3个元素
   t.Log(s2[0], s2[1], s2[2])
   s2 = append(s2, 1)
   t.Log(len(s2), cap(s2))
   t.Log(s2[0], s2[1], s2[2], s2[3])
}

切片共享存储结构

切片共享存储结构

func TestSliceGrowing(t *testing.T) {
   s := []int{}
   for i := 0; i < 10; i++ {
      s = append(s, i)
      t.Log(len(s), cap(s))
   }
}

运行结果

=== RUN   TestSliceGrowing
    slice_test.go:32: 1 1
    slice_test.go:32: 2 2
    slice_test.go:32: 3 4
    slice_test.go:32: 4 4
    slice_test.go:32: 5 8
    slice_test.go:32: 6 8
    slice_test.go:32: 7 8
    slice_test.go:32: 8 8
    slice_test.go:32: 9 16
    slice_test.go:32: 10 16
--- PASS: TestSliceGrowing (0.00s)
PASS
func TestSliceShareMemory(t *testing.T) {
   year := []string{"Jan", "Feb", "Mar", "Apr", "May", "jun", "jul",
      "Aug", "Sep", "Oct", "Nov", "Dec"}
   Q2 := year[3:6]
   t.Log(Q2, len(Q2), cap(Q2))
   summer := year[5:8]
   t.Log(summer, len(summer), cap(summer))
   summer[0] = "Unknow"
   t.Log(Q2)
   t.Log(year)
}

数组VS切片

  1. 容量是否可缩容

    数组不可缩容,切片可以缩容

  2. 是否可进行比较

    数组可以比较,切片不能进行比较

func TestSliceComparing(t *testing.T) {
   a := []int{1, 2, 3, 4}
   b := []int{1, 2, 3, 4}
   if a == b { // invalid operation: a == b (slice can only be compared to nil)
     // 切片只能与nil进行比较
      t.Log("equal")
   }
}

Map声明

m := map[string]int{"one":1,"two":2,"three":3}
m1 := map[string]int{}
m1["one"] = 1 // 给指定的key赋值
m2 := make(map[string]int,10 /*Initial Capacity*/)

cap函数不能用于map

func TestInitMap(t *testing.T) {
   m1 := map[int]int{1: 1, 2: 4, 3: 9}
   t.Log(m1[2])
   t.Logf("len m1=%d", len(m1))
   m2 := map[int]int{}
   m2[4] = 16
   t.Logf("len m2=%d", len(m2))
   m3 := make(map[int]int, 10)
   t.Logf("len m3=%d", len(m3))
}
func TestAccessExistKey(t *testing.T) {
   m1 := map[int]int{}
   // 不存在的key
   t.Log(m1[1]) // 0
   m1[2] = 0
   t.Log(m1[2]) // 0
   if v, ok := m1[3]; ok {
      t.Logf("key 3 is existing,value is %d", v)
   } else {
      t.Log("key 3 is not existing")
   }
   m1[3] = 0
   if v, ok := m1[3]; ok {
      t.Logf("key 3 is existing,value is %d", v)
   } else {
      t.Log("key 3 is not existing")
   }
}

Map遍历

m := map[string]int{"one":1,"two":2,"three":3}
for k,v := range m {
  t.Log(k, v)
}
func TestTravelMap(t *testing.T) {
   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接口方式一起,可以方便的实现单一方法对象的工厂模式
func TestMapWithFunValue(t *testing.T) {
   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. 基本操作
    1. 添加元素
    2. 判断元素是否存在
    3. 删除元素
    4. 元素个数
func TestMapForSet(t *testing.T) {
   mySet := map[int]bool{}
   mySet[1] = true
   n := 3
   // 判断元素是否存在
   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)
   }
}

字符串

与其他主要编程语言的差异

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

Unicode UTF-8

  1. Unicode是一种字符集(code point)
  2. UTF-8是unicode的存储实现(转换为字节序列的规则)
func TestString(t *testing.T) {
   var s string
   t.Log(s) // 初始化为默认零值""
   s = "hello"
   t.Log(len(s)) // 5
   // s[1] ='3' // string是不可变的byte slice
   s = "\xE4\xB8\xA5" // 可以存储任何二进制数据
   // 严字的中文
   // s = "\xE4\xBA\xB5\xFF" // 乱码
   t.Log(s)      // 严
   t.Log(len(s)) // 3
   s = "中"
   t.Log(len(s)) // 3 是byte数
   // rune 可以取出字符串里的unicode
   c := []rune(s)
   t.Log(len(c)) // 1
   // t.Log("rune size:", unsafe.SizeOf(c[0))
   t.Logf("中 unicode %x", c[0]) // 中 unicode 4e2d
   t.Logf("中 UTF-8 %x", s)      // 中 UTF-8 e4b8ad
}

编码与存储

字符 “中”
Unicode 0x4E2D
UTF-8 0xE4B8AD
string/[]byte [0xE4,0xB8,0xAD]

常用字符串函数

  1. strings包(https://golang.org/pkg/strings)
  2. strconv包(https://golang.org/pkg/strconv)
// 字符串遍历
func TestStringToRune(t *testing.T) {
   s := "中华人民共和国"
   for _, c := range s {
     // 第一个参数以%c 和 %d进行格式化
      t.Logf("%[1]c %[1]d", c)
   }
}
=== RUN   TestStringToRune
    string_test.go:29: 中 20013
    string_test.go:29: 华 21326
    string_test.go:29: 人 20154
    string_test.go:29: 民 27665
    string_test.go:29: 共 20849
    string_test.go:29: 和 21644
    string_test.go:29: 国 22269
--- PASS: TestStringToRune (0.00s)
PASS
// 字符串切割和拼接
func TestStringFn(t *testing.T) {
   s := "A,B,C"
   parts := strings.Split(s, ",")
   for _, part := range parts {
      t.Log(part)
   }
   t.Log(strings.Join(parts, "-"))
}

// 字符串和其他类型的转换
func TestConv(t *testing.T) {
   // 整型转字符串
   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. 函数可以作为参数和返回值
func returnMultiValues() (int, int) {
   // 返回两个随机数
   return rand.Intn(10), rand.Intn(20)
}

func TestFn(t *testing.T) {
   a, b := returnMultiValues()
   t.Log(a, b)
}

// 计算其他函数时间差值, 其他函数:输入整型,返回整型 ,本方法返回一个函数类型
func timeSpent(inner func(op int) int) func(op int) int {
   return func(n int) int {
      start := time.Now()
      ret := inner(n)
      fmt.Println("time spent:", time.Since(start).Seconds())
      return ret
   }
}

func slowFun(op int) int {
   time.Sleep(time.Second + 1)
   return op
}

func TestFn1(t *testing.T) {
   tsSF := timeSpent(slowFun)
   t.Log(tsSF(10))
}

推荐书籍:《计算机程序的构造和解释》

可变参数

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 函数(延迟执行函数)

用于释放资源或者释放锁

func TestDefer(t *testing.T){
  // 调用了匿名函数,函数返回前才会执行,类似于java的finally,也可以调用非匿名函数
  defer func(){
    t.Log("clear reources")
  }()
  t.Log("Srarted")
  // panic :程序异常中断
  panic("Fatal error") // defer仍然会执行
}
func Clear() {
   fmt.Println("Clear resources.")
}

func TestDefer(t *testing.T) {
   defer Clear()
   fmt.Println("Start")
}

封装数据和行为

结构体定义

type Employee struct {
  Id string
  Name string
  Age int
}

实例创建及初始化

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"

行为(方法)定义

与其他语言主要差异

// 第一种定义方式在实例对应方法被调用时,实例的成员会进行复制
func (e Employee) String() string {
  return fmt.Sprintf("ID:%s-Name:%s-Age:%d",e.Id,e.Name,e.Age)
}

// 通常情况下为了避免内存拷贝,使用第二种定义方式
// 参数定义了实例的指针
func (e *Employee) String() string {
  return fmt.Sprintf("ID:%s/Name:%s/Age:%d",e.Id,e.Name,e.Age)
}
func (e Employee) String() string {
   fmt.Printf("Address is %x \n", unsafe.Pointer(&e.Name))
   return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}

func (e *Employee) String1() string {
   fmt.Printf("Address1 is %x \n", unsafe.Pointer(&e.Name))
   return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}

func TestStructOperations(t *testing.T) {telcl
   e := Employee{"0", "Bob", 20}
   fmt.Printf("e Address is %x \n", unsafe.Pointer(&e.Name)) // 14000108490
   t.Log(e.String())                                         // 140001084c0
   t.Log(e.String1())                                        // 14000108490
   e1 := &Employee{"0", "Bob", 20}
   fmt.Printf("e1 Address is %x \n", unsafe.Pointer(&e1.Name)) // 14000068520
   t.Log(e1.String())                                          // 14000068550
   t.Log(e1.String1())                                         //14000068520
}

接口与依赖

Duck Type式接口实现

接口定义

type Programmer interface {
  WriteHelloWorld() Code
}

接口实现

type GoProgrammer struct {
  
}
func (p * GoProgrammer) WriteHelloWorld() Code {
  return "fmt.Println(\"Hello World\")"
}
type Programmer interface {
   WriteHelloWorld() string
}

type GoProgrammer struct {
}
// 方法签名一样就可以认为是接口的实现
// 可以先实现方法,后面再加interface的定义
func (g *GoProgrammer) WriteHelloWorld() string {
   return "fmt.Println(\"Hello World\")"
}

func TestClient(t *testing.T) {
   var p Programmer
   p = new(GoProgrammer)
   t.Log(p.WriteHelloWorld())
}

与其他主要编程语言的差异

  1. 接口为非入侵,实现不依赖于接口定义
  2. 所以接口的定义可以包含在接口使用者包内

接口变量

接口变量

自定义类型

  1. type IntConvertionFn func(n int) int
  2. Type MyPoint int
// 定义别名
type IntConv func(op int) int

func timeSpent(inner IntConv) func(op int) int {
   return func(n int) int {
      start := time.Now()
      ret := inner(n)
      fmt.Println("time spent:", time.Since(start).Seconds())
      return ret
   }
}

func slowFun(op int) int {
   time.Sleep(time.Second + 1)
   return op
}

func TestFn1(t *testing.T) {
   tsSF := timeSpent(slowFun)
   t.Log(tsSF(10))
}

扩展与复用

type Pet struct {
}

func (p *Pet) Speak() {
   fmt.Println("...")
}

func (p *Pet) SpeakTo(host string) {
   p.Speak()
   fmt.Println(" ", host)
}

type Dog struct {
   p *Pet
}

func (d *Dog) Speak() {
  d.p.Speak()
}

func (d *Dog) SpeakTo(host string) {
   d.p.SpeakTo(host)
}

func TestDog(t *testing.T) {
   dog := new(Dog)
   dog.SpeakTo("wang")
}
=== RUN   TestDog
...
  young
--- PASS: TestDog (0.00s)
PASS
func (d *Dog) Speak() {
   fmt.Println("wang")
}

func (d *Dog) SpeakTo(host string) {
   d.Speak()
   fmt.Println(" ", host)
}

func TestDog(t *testing.T) {
   dog := new(Dog)
   dog.SpeakTo("wang")
}
=== RUN   TestDog
wang
  wang
--- PASS: TestDog (0.00s)
PASS
// 匿名嵌套类型
type Dog struct {
   Pet
}

func TestDog(t *testing.T) {
   dog := new(Dog)
   dog.Speak()
   dog.SpeakTo("wang")
}
=== RUN   TestDog
...
  wang
--- PASS: TestDog (0.00s)
PASS
// 匿名嵌套类型
type Dog struct {
   Pet
}

func (d *Dog) Speak() {
   fmt.Println("Wang!")
}

func TestDog(t *testing.T) {
   dog := new(Dog)
   dog.Speak()
   dog.SpeakTo("wang")
}
=== RUN   TestDog
Wang!
...
  wang
--- PASS: TestDog (0.00s)
PASS

不支持重载

不支持类似于Java的 父类 = new 子类的模式

多态

type Code string
type Programmer interface {
   WriteHelloWorld() Code
}

type GoProgrammer struct {
}

func (g *GoProgrammer) WriteHelloWorld() Code {
   return "fmt.Println(\"Hello World\")"
}

type JavaProgrammer struct {
}

func (g *JavaProgrammer) WriteHelloWorld() Code {
   return "System.out.Println(\"Hello World\")"
}

// Programmer是一个interface,所以他只能是一个指针类型
func writeFirstProgrammer(p Programmer) {
  // %T 实例类型
   fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}

func TestPolymorphism(t *testing.T) {
  // writeFirstProgrammer的参数只能是指针类型,所以创建方法为new()或者&GoProgrammer{}
   goProg := new(GoProgrammer)
   javaProg := new(JavaProgrammer)
   writeFirstProgrammer(goProg)
   writeFirstProgrammer(javaProg)
}
=== RUN   TestPolymorphism
*polymorphism.GoProgrammer fmt.Println("Hello World")
*polymorphism.JavaProgrammer System.out.Println("Hello World")
--- PASS: TestPolymorphism (0.00s)
PASS

空接口与断言

  1. 空接口可以表示任何类型

  2. 通过断言来将空接口转换为指定类型

    V,ok:=p.(int) // ok=true时为转换成功

类似于java的Object类型或者c/cpp中的void*

func DoSomething(p interface{}) {
   if i, ok := p.(int); ok {
      fmt.Println("Integer", i)
      return
   }
   if s, ok := p.(string); ok {
      fmt.Println("string", s)
      return
   }
   fmt.Println("Unknow Type")
}

func TestEmpty(t *testing.T) {
   DoSomething(10)
   DoSomething("10")
}
=== RUN   TestEmpty
Integer 10
string 10
--- PASS: TestEmpty (0.00s)
PASS
func DoSomething(p interface{}) {
   switch v := p.(type) {
   case int:
      fmt.Println("Integer", v)
   case string:
      fmt.Println("string", v)
   default:
      fmt.Println("Unknow type")
   }
}

Go接口最佳实践

倾向于使用小的接口定义,很多接口只包含一个方法

type Reader interface {
  Read(p []byte) (n int,err error)
}
type Writer interface {
  Write(p []byte) (n int,err error)
}

较大的接口定义,可以由多个小接口定义组合而成

type ReadWrite interface {
  Reader
  Writer
}

只依赖于必要功能的最小接口

func StoreDate(reader Reader) error{
  ...
}

Go的错误机制

与其他主要编程语言的差异

  1. 没有异常机制
  2. error类型实现了error接口
  3. 可以通过errors.New来快速创建错误实例
type error interface{
  Error() string
}

errors.New("n must be in the range[0,10]")
func GetFibonacci(n int) []int {
   fibList := []int{1, 1}
   for i := 2; i < n; i++ {
      fibList = append(fibList, fibList[i-2], +fibList[i-1])
   }
   return fibList
}
// 结果不符合期待
func TestGetFibonacci(t *testing.T) {
   t.Log(GetFibonacci(-10))
}

增加错误返回

func GetFibonacci(n int) ([]int, error) {
   if n < 2 || n > 100 {
      return nil, errors.New("n should be in [2,100]")
   }
   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 {
      t.Error(err)
   } else {
      t.Log(v)
   }
}

判断不同的错误

var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThanHundredError = 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, LargerThanHundredError
   }
   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指定的函数
  • os.Exit退出时不会输出当前调用栈的信息
func TestPanicVxExit(t *testing.T) {
	//defer func() {
	//	fmt.Println("final")
	//}()
	fmt.Println("start")
	//panic(errors.New("Something wrong"))
	os.Exit(-1)
	fmt.Println("End")
}

recover

Java:

try{
  ...
}catch(Throwable t){
  
}

C++

try{
  ...
}catch(...){
  
}

go:

defer func(){
  // recover 拿到的是panic里拿到的错误
  if err:=recover();err!=nil{
    // 恢复错误
  }
}()
=== RUN   TestPanicVxExit
start
recovered from  Something wrong
--- PASS: TestPanicVxExit (0.00s)
PASS

当心recover成为恶魔

  • 形成僵尸服务进程,导致health check失效
  • “Let it Cresh”(杀掉程序,让守护进程重启服务)往往是恢复不确定性错最好的方法

Package

  1. 基本复用模块单元

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

  2. 代码的package可以和所在的目录不一致

  3. 同一目录里的Go代码的package要保持一致

需要配置GOPATH,go会去从GOPATH的src下找路径

package

  1. 通过go get 来获取远程依赖
    • go get -u 强制从远程网络更新远程依赖
  2. 注意代码在github上的组织形式,以适应go get
    • 直接以代码路径开始,不要有src

示例:https://github.com/easierway/concurrent_map

/ch15/series/my_series.go

package series

func GetFibonacci(n int) []int {
   fibList := []int{1, 1}
   for i := 2; i < n; i++ {
      fibList = append(fibList, fibList[i-2], +fibList[i-1])
   }
   return fibList
}

func Square(n int) int {
   return n * n
}

ch15/client/package_test.go

package client

import (
   "ch15/series"
   "testing"
)

func TestPackage(t *testing.T) {
   t.Log(series.GetFibonacci(5))
   t.Log(series.Square(5))
}

init方法

  • 在main被执行前, 所有依赖的package的init方法都会被执行
  • 不同包的init函数按照包绕如的依赖关系决定执行顺序
  • 每个包可以有多个init函数
  • 包的每个源文件也可以有多个init函数
package series

import "fmt"

func init() {
	fmt.Println("init1")
}

func init() {
	fmt.Println("init2")
}

func GetFibonacci(n int) []int {
	fibList := []int{1, 1}
	for i := 2; i < n; i++ {
		fibList = append(fibList, fibList[i-2], +fibList[i-1])
	}
	return fibList
}

func Square(n int) int {
	return n * n
}

package client

import (
   "ch15/series"
   "testing"
)

func TestPackage(t *testing.T) {
   t.Log(series.GetFibonacci(5))
   t.Log(series.Square(5))
}
init1
init2
=== RUN   TestPackage
    package_test.go:9: [1 1 1 1 1 1 1 1]
    package_test.go:10: 25
--- PASS: TestPackage (0.00s)
PASS

依赖管理

未解决的依赖问题

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

vendor路径

随着Go 1.5release版本的发布,vendor目录被添加到除了GOPATH和GOROOT之外的依赖目录查找解决方案。在Go1.6之前,需要手动设置环境变量

查找依赖包了路径的解决方案如下

  1. 当前包下的vendor目录
  2. 向上级目录查找,直到找到src下的vendor目录
  3. 在GOPATH下面查找依赖包
  4. 在GOROOT目录下查找

常用的依赖管理工具

godep https://github.com/tools/godep

glide https://github.com/Masterminds/glide

dep https://github.com/golang/dep

协程

Thread Vs Groutine

  1. 创建时默认的stack(栈)的大小
    • JDK5以后Java Thread stack默认为1M
    • Groutine的Stack初始化大小为2K
  2. 和KSE(kernel Space Entiry)的对应关系
    • Java Thread 是1:1
    • Groutine是M:N

线程、协程与内核对象的映射关系
Kernel entity是有cpu直接进行调度,调度效率很高,如果线程对象发生切换,会涉及到内核对象的切换,消耗会很高。

如果多个线程对象对应一个内核对象,那么他们之间的消耗就会小很多

Go的线程调度机制

M:系统线程,也就是kernel entity

P:Go语言实现的协程处理器

G:协程

每个处理器都挂着一个协程队列,有一个协程是正在运行状态,processor依次运行这些协程

Go程序启动的时候,会有一个守护线程,去记录每个processor完成的协程的数量,当发现一段时间,某个processor完成的协程数量没有变化的时候,就会往协程的任务栈中插入一个特别的标记,当协程运行时,遇到非内联函数的时候,就会读到这个标记,就会将自己中断,把自己插入到等候协程队列的队尾,切换成别的协程继续运行。

当某一个协程被系统中断了,比如IO,需要等待的时候,processor会将自己移动到另一个可使用的系统线程中,继续执行协程队列中的其他协程,当被中断的协程被唤醒,完成之后,他会把自己加入到其中某一个processor的协程等待队列中,或者加入到全局等待队列当中

当一个协程被中断的时候,他在寄存器里的运行状态会保存到这个协程对象里,当协程再次获得运行的机会的时候,状态就会重新写入寄存器然后继续运行

package groutine_test

import (
   "fmt"
   "testing"
   "time"
)

func TestGroutine(t *testing.T) {
   for i := 0; i < 10; i++ {
      go func(i int) {
         fmt.Println(i)
      }(i)
   }
   time.Sleep(time.Millisecond * 50)
}
=== RUN   TestGroutine
9
5
6
7
8
1
0
2
3
4
--- PASS: TestGroutine (0.05s)
PASS
func TestGroutine(t *testing.T) {
   for i := 0; i < 10; i++ {
      go func() {
         fmt.Println(i)
      }()
   }
   time.Sleep(time.Millisecond * 50)
}
=== RUN   TestGroutine
10
10
10
10
10
10
10
10
10
10
--- PASS: TestGroutine (0.05s)
PASS

这里打印出来都是10,这是因为i这个变量,在test所在的协程以及它启动的其他的协程里面,被共享了,共享变量就存在竞争条件,这样的话,就需要用锁的机制来完成。

而第一种编程模式没有问题,是因为,Go的方法调用传递都是值传递,传递的i是被复制了一份,每个协程中所拥有的的变量地址是不一样的,没有竞争关系。

内存共享与并发机制

packge : sync

Mutex

RWLock

package share_mem

import (
   "testing"
   "time"
)

func TestCounter(t *testing.T) {
   counter := 0
   for i := 0; i < 5000; i++ {
      go func() {
         counter++
      }()
   }
   time.Sleep(1 * time.Second)
   t.Logf("Counter = %d", counter)
}
=== RUN   TestCounter
    share_mem_test.go:16: Counter = 4746
--- PASS: TestCounter (1.00s)
PASS

counter在不同的协程中自增,存在线程安全问题,需要加锁对共享内容进行保护。

func TestCounterThreadSafe(t *testing.T) {
  // 使用Mutex进行加锁
   var mut sync.Mutex
   counter := 0
   for i := 0; i < 5000; i++ {
      go func() {
        // 声明defer函数,类似于java中的finally
         defer func() {
            mut.Unlock()
         }()
         mut.Lock()
         counter++

      }()
   }
   time.Sleep(1 * time.Second)
   t.Logf("Counter = %d", counter)
}
=== RUN   TestCounterThreadSafe
    share_mem_test.go:23: Counter = 5000
--- PASS: TestCounter (1.00s)
PASS

WaitGroup

var wg sync.WaitGroup
for i := 0; i < 5000; i++ {
  wg.Add(1)
  go func(){
    defer func(){
      wg.Done()
    }()
    ...
  }()
}
wg.Wait()

java中等待其他线程,然后继续往下执行使用join,WaitGroup功能类似于join

只有当wait的所有东西都完成了之后,才能继续往下执行

之前的代码中,都利用Sleep来保证可以得到正确的结果

package share_mem

import (
   "sync"
   "testing"
)

func TestCounterThreadSafe(t *testing.T) {
   var mut sync.Mutex
   counter := 0
   for i := 0; i < 5000; i++ {
      go func() {
         defer func() {
            mut.Unlock()
         }()
         mut.Lock()
         counter++

      }()
   }
   //time.Sleep(1 * time.Second)
   t.Logf("Counter = %d", counter)
}
=== RUN   TestCounterThreadSafe
    share_mem_test.go:22: Counter = 4988
--- PASS: TestCounter (0.00s)
PASS

协程执行的速度超过了线程执行完的速度,所以加入了一个时间等待

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++
        // 等待完成,告诉WaitGroup等待结束
         wg.Done()
      }()
   }
  // 阻塞等待全部任务完成
   wg.Wait()
   t.Logf("Counter = %d", counter)
}
=== RUN   TestCounterWaitGroup
    share_mem_test.go:41: Counter = 5000
--- PASS: TestCounterWaitGroup (0.00s)
PASS

第一种方法不知道要等待多久,所以强制等待了1s

第二种方法可以准确的知道什么时候会执行结束

CSP并发控制

Communication sequential processes

依赖于一个通道完成两个通信实体之间的协调
ActorModel

CSP Vs Actor

  • 和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些
  • Go中channel是有容量限制并且独立于处理Groutine,而如Erlang,Actor模式中的mailbox容量是无限的没接收进程也总是被动地处理消息

channel

类似于java的Futura

package csp

import (
   "fmt"
   "testing"
   "time"
)

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 TestService(t *testing.T) {
   fmt.Println(service())
   otherTask()
}

串行的执行

=== RUN   TestService
Done
working on something else
Task is Done
--- PASS: TestService (0.15s)
PASS
func AsyncService() chan string {
   // 声明一个channel,第二个参数是channel的类型
   retCh := make(chan string)
   // 被调用时启动另外一条协程去运行,而不阻塞当前协程
   go func() {
      ret := service()
      fmt.Println("returned result")
      // 运行完之后将结果放到channel中,会阻塞,直到消息完成才会想下执行
      retCh <- ret
      fmt.Println("service exited")
   }()
   // 返回channel
   return retCh
}

func TestAsyncService(t *testing.T) {
   // 返回channel
   retch := AsyncService()
   otherTask()
   // 需要channel的结果时候将他取出来
   fmt.Println(<-retch)
   time.Sleep(time.Second * 1)
}

并行执行

=== RUN   TestAsyncService
working on something else
returned result
Task is Done
Done
service exited
--- PASS: TestAsyncService (1.10s)
PASS

此时可以看到,service exited在最后输出,即时serivce把数据放入的channel,其他程序没有去接收这个channel,这个协程就被阻塞住了

将channel声明为buffer channel,唯一的区别是多了一个容量的大小

func AsyncService() chan string {
   // 声明一个channel,第二个参数是channel的类型
   retCh := make(chan string, 1)
   // 被调用时启动另外一条协程去运行,而不阻塞当前协程
   go func() {
      ret := service()
      fmt.Println("returned result")
      // 运行完之后将结果放到channel中,会阻塞,直到消息完成才会想下执行
      retCh <- ret
      fmt.Println("service exited")
   }()
   // 返回channel
   return retCh
}
=== RUN   TestAsyncService
working on something else
returned result
service exited
Task is Done
Done
--- PASS: TestAsyncService (1.10s)
PASS

service exited在returned result之后就执行了,并没有阻塞service

多路选择与超时

select

多渠道的选择

select {
  case ret:=<-retCh1:
	  t.logf("result %s",ret)
  case ret:=<-retCh2:
	  t.logf("result %s",ret)
  default:
	  t.Error("No one returned")
}

只要任意一个Channel可以往下执行,就会执行这个case的返回,顺序不能保证

超时控制

select {
  case ret :=<-retCh:
  	t.Logf("result %s",ret)
  case <-time.After(time.Second*1)
  t.Error("time out")
}
package _select

import (
   "fmt"
   "testing"
   "time"
)

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 TestService(t *testing.T) {
   fmt.Println(service())
   otherTask()
}

func AsyncService() chan string {
   // 声明一个channel,第二个参数是channel的类型
   retCh := make(chan string, 1)
   // 被调用时启动另外一条协程去运行,而不阻塞当前协程
   go func() {
      ret := service()
      fmt.Println("returned result")
      // 运行完之后将结果放到channel中,会阻塞,直到消息完成才会想下执行
      retCh <- ret
      fmt.Println("service exited")
   }()
   // 返回channel
   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的关闭

  • 向关闭的channel发送数据,会导致panic

  • v,ok-<ch;ok为bool值,true表示正常接收,false表示通道关闭

  • 所有的channel接受者都会在channel关闭时,立即从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行向多个订阅者同时发送信号。

    如:退出信息

如果channel被关闭,再去接收,会返回通道定义类型的零值

package channel_close

import (
   "fmt"
   "sync"
   "testing"
)

func dataProducer(ch chan int, wg *sync.WaitGroup) {
   go func() {
      for i := 0; i < 10; i++ {
         ch <- i
      }
      wg.Done()
   }()
}

func dateReceiver(ch chan int, wg *sync.WaitGroup) {
   go func() {
      for i := 0; i < 10; i++ {
         data := <-ch
         fmt.Println(data)
      }
      wg.Done()
   }()
}

func TestCloseChannel(t *testing.T) {
   var wg sync.WaitGroup
   ch := make(chan int)
   wg.Add(1)
   dataProducer(ch, &wg)
   wg.Add(1)
   dateReceiver(ch, &wg)
   wg.Wait()
}
package channel_close

import (
   "fmt"
   "sync"
   "testing"
)

func dataProducer(ch chan int, wg *sync.WaitGroup) {
   go func() {
      for i := 0; i < 10; i++ {
         ch <- i
      }
      close(ch)
      wg.Done()
   }()
}

func dateReceiver(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()
   }()
}

func TestCloseChannel(t *testing.T) {
   var wg sync.WaitGroup
   ch := make(chan int)
   wg.Add(1)
   dataProducer(ch, &wg)
   wg.Add(1)
   dateReceiver(ch, &wg)
   wg.Add(1)
   dateReceiver(ch, &wg)
   wg.Wait()
}

任务的取消

package cancel_by_close

import (
   "fmt"
   "testing"
   "time"
)

func isCancelled(cancelChan chan struct{}) bool {
   select {
   case <-cancelChan:
      return true
   default:
      return false
   }
}

func cancel_1(cancelChan chan struct{}) {
   cancelChan <- struct{}{}
}


func TestCancel(t *testing.T) {
   cancelChan := make(chan struct{}, 0)
   for i := 0; i < 5; i++ {
      go func(i int, cancelChan chan struct{}) {
         for {
            if isCancelled(cancelChan) {
               break
            }
            time.Sleep(time.Millisecond * 5)
         }
         fmt.Println(i, "Cancelled")
      }(i, cancelChan)
   }
   cancel_1(cancelChan)
   time.Sleep(time.Second * 1)
}
=== RUN   TestCancel
4 Cancelled
--- PASS: TestCancel (1.00s)
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, cancelChan chan struct{}) {
         for {
            if isCancelled(cancelChan) {
               break
            }
            time.Sleep(time.Millisecond * 5)
         }
         fmt.Println(i, "Cancelled")
      }(i, cancelChan)
   }
   cancel_2(cancelChan)
   time.Sleep(time.Second * 1)
}
=== RUN   TestCancel
4 Cancelled
0 Cancelled
1 Cancelled
2 Cancelled
3 Cancelled
--- PASS: TestCancel (1.00s)
PASS

context与任务取消

关联任务的取消

关联任务的取消
Golang 1.9之后将Context内置了

  • 根Context:通过context.Background()创建
  • 子Context:context.WithCancel(parentContext)创建
    • ctx,cancel:=context.WithCancel(context.BackGround())
  • 当前Context被取消时,基于他的子context都会被取消
  • 接收取消通知<-ctx.Done()
import (
   "context"
   "fmt"
   "testing"
   "time"
)

func isCancelled(ctx context.Context) bool {
   select {
   case <-ctx.Done():
      return true
   default:
      return false
   }
}

func TestCancel(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)
}

仅运行一次

确保多线程环境下,某段代码只执行一次

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

var once sync.Once
var obj *SingletonObj

func GetSingletonObj() *SingletonObj{
  once.Do(func(){
    fmt.Println("Create Singleton obj.")
    obj &SingletonObj{}
  })
  return obj
}
import (
   "fmt"
   "sync"
   "testing"
   "unsafe"
)

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 TestGetSingletonObj(t *testing.T) {
   var wg sync.WaitGroup
   for i := 0; i < 10; i++ {
      wg.Add(1)
      go func() {
         obj := GetSingletonObj()
        // 打印地址
         fmt.Printf("%d\n", unsafe.Pointer(obj))
         wg.Done()
      }()
   }
   wg.Wait()
}
=== RUN   TestGetSingletonObj
Create Obj
4307408024
4307408024
4307408024
4307408024
4307408024
4307408024
4307408024
4307408024
4307408024
4307408024
--- PASS: TestGetSingletonObj (0.00s)
PASS

仅需任意任务完成

import (
   "fmt"
   "testing"
   "time"
)

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)
   for i := 0; i < numberOfRunner; i++ {
      go func(i int) {
         ret := runTask(i)
         ch <- ret
      }(i)
   }
   return <-ch
}

func TestFirstResponse(t *testing.T) {
   t.Log(FirstResponse())
}
=== RUN   TestFirstResponse
    tirst_response_test.go:27: the result is from 2
--- PASS: TestFirstResponse (0.01s)
PASS

存在的问题:其余未被使用的线程仍被阻塞着等待被接收

func TestFirstResponse(t *testing.T) {
   t.Log("Before:", runtime.NumGoroutine())
   t.Log(FirstResponse())
   time.Sleep(time.Second * 1)
   t.Log("After:", runtime.NumGoroutine())
}
=== RUN   TestFirstResponse
    tirst_response_test.go:28: Before: 2
    tirst_response_test.go:29: the result is from 3
    tirst_response_test.go:31: After: 11
--- PASS: TestFirstResponse (1.01s)
PASS

解决方案,使用buffered channel,buffer channel 生产者不用等待接受者将消息拿走

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 TestFirstResponse(t *testing.T) {
   t.Log("Before:", runtime.NumGoroutine())
   t.Log(FirstResponse())
   time.Sleep(time.Second * 1)
   t.Log("After:", runtime.NumGoroutine())
}
=== RUN   TestFirstResponse
    tirst_response_test.go:28: Before: 2
    tirst_response_test.go:29: the result is from 4
    tirst_response_test.go:31: After: 2
--- PASS: TestFirstResponse (1.01s)
PASS 

所有任务完成

可以使用WaitGroup,也可以使用channel

import (
   "fmt"
   "runtime"
   "testing"
   "time"
)

func runTask(id int) string {
   time.Sleep(10 * time.Millisecond)
   return fmt.Sprintf("the result is from %d", id)
}

func ALlResponse() string {
   numberOfRunner := 10
   ch := make(chan string, numberOfRunner)
   for i := 0; i < numberOfRunner; i++ {
      go func(i int) {
         ret := runTask(i)
         ch <- ret
      }(i)
   }
   finalRet := ""
   for i := 0; i < numberOfRunner; i++ {
      finalRet += <-ch + "\n"
   }
   return finalRet
}

func TestFirstResponse(t *testing.T) {
   t.Log("Before:", runtime.NumGoroutine())
   t.Log(ALlResponse())
   time.Sleep(time.Second * 1)
   t.Log("After:", runtime.NumGoroutine())
}

对象池

使用buffered channel实现对象池

使用buffered channel实现对象池
obj_pool.go

import (
   "errors"
   "time"
)

type ReusableObj struct {
}

type ObjPool struct {
   bufChan chan *ReusableObj // 用于缓冲可重用对象
}

func NewObjPool(numOfObj int) *ObjPool {
   objPool := ObjPool{}
   objPool.bufChan = make(chan *ReusableObj, numOfObj)
   for i := 0; i < numOfObj; 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")
   }
}

obj_pool_test.go

import (
   "fmt"
   "testing"
   "time"
)

func TestObjPool_GetObj(t *testing.T) {
   pool := NewObjPool(10)
   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

sync.Pool对象缓存

sync.Pool

sync.Pool对象获取

  • 尝试从私有对象获取
  • 私有对象不存在,尝试从当前Processor的共享池获取
  • 如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取
  • 如果所有子池的是空的,最后就用用户指定的New函数产生一个新的对象返回

Sync.Pool对象放回

  • 如果私有对象不存在,则保存为私有对象
  • 如果私有对象存在,放入当前Processor子池的共享池中

使用sync.Pool

pool:=&sync.Pool {
  New: func() interface{}{
    return 0
  },
}

arry:=pool.Get().(int)
...
pool.Put(10)

sync.Pool对象的生命周期

  • GC会清除sync.pool缓存的对象
  • 对象的缓存有效期为下一次GC之前
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)
   v1, _ := pool.Get().(int)
   fmt.Println(v1)
}
=== RUN   TestSyncPool
Create a new Object
100
3
--- PASS: TestSyncPool (0.00s)
PASS
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)
}
=== RUN   TestSyncPool
Create a new Object
100
Create a new Object
100
--- PASS: TestSyncPool (0.00s)
PASS
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)
	// 不做put操作,私有对象池是没有的
   v2, _ := pool.Get().(int)
   fmt.Println(v2)
}
=== RUN   TestSyncPool
Create a new Object
100
3
Create a new Object
100
--- PASS: TestSyncPool (0.00s)
PASS
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) {
         fmt.Println(pool.Get())
         wg.Done()
      }(i)
   }
   wg.Wait()
}
=== RUN   TestSyncPoolInMultiGroutine
100
100
100
Create a new object
10
Create a new object
10
Create a new object
10
Create a new object
10
Create a new object
10
Create a new object
10
Create a new object
10
--- PASS: TestSyncPoolInMultiGroutine (0.00s)
PASS

sync.Pool总结

  • 适合于通过复用,降低复杂对象的创建和GC代价
  • 协程安全,会有锁的开销
  • 生命周期受GC影响,不适合于做连接池等需要自己管理生命周期的资源的池化

单元测试

funtions.go

package testing

func square(op int) int {
   return op * op + 1
}

functions_test.go

ackage testing

import "testing"

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

内置单元测试框架

  • Fail,Error:该测试失败,该测试继续,其他测试继续执行
  • FailNow,Fatal:该测试失败,该测试终止,其他测试继续执行
func TestErrorInCode(t *testing.T) {
   fmt.Println("Start")
   t.Error("Error")
   fmt.Println("end")
}
=== RUN   TestErrorInCode
Start
    function_test.go:22: Error
end
--- FAIL: TestErrorInCode (0.00s)

FAIL
func TestFailInCode(t *testing.T) {
   fmt.Println("Start")
   t.Fatalf("Error")
   fmt.Println("end")
}
=== RUN   TestFailInCode
Start
    function_test.go:28: Error
--- FAIL: TestFailInCode (0.00s)

FAIL

Benchmark

与Benchmark 开头,使用testing.B

func BenchmarkConcatStringByAdd(b * testing.B) {
 // 与性能测试的无关的代码
 b.ResetTimer()
 for i:=0;i<b.N;i++ {
   // 测试代码
 }
 b.StopTimer()
 // 与性能测试无关的代码
}
import (
   "bytes"
   assert2 "github.com/stretchr/testify/assert"
   "testing"
)

func TestConcatStringByAdd(t *testing.T) {
   assert := assert2.New(t)
   elems := []string{"1", "2", "3", "4", "5"}
   ret := ""
   for _, elem := range elems {
      ret += elem
   }
   assert.Equal("12345", ret)
}

func TestConcatStringByBytestBuffer(t *testing.T) {
   assert := assert2.New(t)
   var buf bytes.Buffer
   elems := []string{"1", "2", "3", "4", "5"}
   for _, elem := range elems {
      buf.WriteString(elem)
   }
   assert.Equal("12345", buf.String())
}

func BenchmarkConcatStringByAdd(b *testing.B) {
   elems := []string{"1", "2", "3", "4", "5"}
   b.ResetTimer()
   for i := 0; i < b.N; i++ {
      ret := ""
      for _, elem := range elems {
         ret += elem
      }
   }
   b.StopTimer()
}

func BenchmarkConcatStringByBytestBuffer(b *testing.B) {
   elems := []string{"1", "2", "3", "4", "5"}
   b.ResetTimer()
   for i := 0; i < b.N; i++ {
      var buf bytes.Buffer
      for _, elem := range elems {
         buf.WriteString(elem)
      }
   }
   b.StopTimer()
}
go test -bench=.
goos: darwin
goarch: arm64
pkg: ch35/benchmark
BenchmarkConcatStringByAdd-10                   13010373                86.18 ns/op
BenchmarkConcatStringByBytestBuffer-10          29440657                40.81 ns/op
PASS
ok      ch35/benchmark  3.059s

go test -bench=. -benchemem

-bench=<相关benchmark测试>

.表示所有

Windows下使用go test 命令行时,-bench=.应写为-bench=“.”

go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: ch35/benchmark
BenchmarkConcatStringByAdd-10                   13631025                87.66 ns/op           16 B/op          4 allocs/op
BenchmarkConcatStringByBytestBuffer-10          28497046                40.85 ns/op           64 B/op          1 allocs/op
PASS
ok      ch35/benchmark  2.578s

BDD

BDD in Go

项目网站:https://github.com/smartystreets/goconvey

安装

go get -u github.com/smartystreets/goconvey/convey

启动 WEB UI

$GOPATH/bin/goconvey

import (
   "github.com/smartystreets/goconvey/convey"
   "testing"
)

func TestSpec(t *testing.T) {
   convey.Convey("Given 2 even numbers", t, func() {
      a := 2
      b := 4
      convey.Convey("When add the two numbers", func() {
         c := a + b
         convey.Convey("Then the result is still even", func() {
            convey.So(c%2, convey.ShouldEqual, 0)
         })
      })
   })
}

反射编程

reflect.TypeOf Vs reflect.ValueOf

  • reflect.TypeOf返回类型(reflect.Type)
  • reflect.ValueOf返回值(reflect.Value)
  • 可以从reflect.Value获得类型
  • 可以通过kind来判断类型

判断类型-Kind()

const(
  Invalid Kind = iota
  Bool
  Int
  Int8
  Int16
  ...
)
func CheckType(v interface{}) {
   t := reflect.TypeOf(v)
   switch t.Kind() {
   case reflect.Float32, reflect.Float64:
      fmt.Println("Float")
   case reflect.Int, reflect.Int32, reflect.Int64:
      fmt.Println("Integer")
   default:
      fmt.Println("Unkonwn", t)
   }
}

func TestBasicType(t *testing.T) {
   var f float64 = 12
   CheckType(f)
}
=== RUN   TestBasicType
Float
--- PASS: TestBasicType (0.00s)
PASS
func TestBasicType(t *testing.T) {
   var f float64 = 12
   CheckType(&f)
}
=== RUN   TestBasicType
Unkonwn *float64
--- PASS: TestBasicType (0.00s)
PASS
func TestTypeAndValue(t *testing.T) {
   var f int64 = 10
   t.Log(reflect.TypeOf(f), reflect.ValueOf(f))
   t.Log(reflect.ValueOf(f).Type())
}
=== RUN   TestTypeAndValue
    reflect_test.go:28: int64 10
    reflect_test.go:29: int64
--- PASS: TestTypeAndValue (0.00s)
PASS

利用反射编写灵活的代码

按名字访问结构的成员

reflect.ValueOf(*e).FieldByName("Name")

按名字访问结构的方法

reflect.Valueof(e).MethodByName("UpdateAge").call([]reflect.Value{reflect.ValueOf(1)})
type Employee struct {
   EmployeeID string
   Name       string //`format:"normal"`
   Age        int
}

func (e *Employee) UpdateAge(newVal int) {
   e.Age = newVal
}

func TestInvokeByName(t *testing.T) {
   e := &Employee{"1", "Mike", 30}
   t.Logf("Name:value(%[1]v),Type(%[1]T)", reflect.ValueOf(*e).FieldByName("Name"))
   if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
      t.Error("Failed to get 'name' field.")
   } else {
      t.Log("Tag:format", nameField.Tag.Get("format"))
   }
   reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
   t.Log("Update Age:", e)
}
=== RUN   TestInvokeByName
    reflect_test.go:21: Name:value(Mike),Type(reflect.Value)
    reflect_test.go:25: Tag:format 
    reflect_test.go:28: Update Age: &{1 Mike 1}
--- PASS: TestInvokeByName (0.00s)
PASS

Struct Tag

type BasicInfo struct{
  Name String `json:"name"`
  Age int `json:"age"`
}

万能程序

DeepEqual

比较切片和map

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))
}
=== RUN   TestDeepEqual
    flexible_reflect_test.go:12: false
    flexible_reflect_test.go:16: s1 == s2? true
    flexible_reflect_test.go:17: s1 == s3? false
--- PASS: TestDeepEqual (0.00s)
PASS

反射赋值

type Employee struct {
   EmployeeID string
   Name       string `format:"normal"`
   Age        int
}

func (e *Employee) UpdateAge(newVal int) {
   e.Age = newVal
}

type Customer struct {
   CookieID string
   Name     string `format:"normal"`
   Age      int
}

func TestFillNameAndAge(t *testing.T) {
   settings := map[string]interface{}{"Name": "Mike", "Age": 40}
   e := Employee{}
   if err := fillBySettings(&e, settings); err != nil {
      t.Fatal(err)
   }
   t.Log(e)
   c := new(Customer)
   if err := fillBySettings(c, settings); err != nil {
      t.Fatal(err)
   }
   t.Log(*c)
}

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 the 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
      }
      if field.Type == reflect.TypeOf(v) {

         vstr := reflect.ValueOf(st)
         vstr = vstr.Elem()
         vstr.FieldByName(k).Set(reflect.ValueOf(v))
      }
   }
   return nil
}

不安全编程

不安全行为的危险性

i:=10
f:=*(*float64)(unsafe.Poniter(&i))