0 参考资料 GO开源项目收集 :https://github.com/avelino/awesome-go
01 第一章 Go语言简介 1.1 工作区和GOPATH 工作区是放置Go源码文件的目录 工作区下边有pkg、src、bin三个目录
用于存放归档文件(名称以.a为后缀的文件),所有归档文件都会被存放到该目录下的平台相关目录中,同样以代码包为组织形式
用于存放当前工作区中的Go程序的可执行文件
当环境变量GOBIN已有效设置时,该目录会变的无意义
当GOPATH的值中包含多个工作区的路径时,必须设置GOBIN,否则无法成功安装Go程序的可执行文件
1.2 代码包的导入
1 2 3 import . "strings" HasPrefix("abc" , "a" )
1.3 包管理 1.3.1 Go的依赖管理
1.3.2 go mod 1 2 3 4 5 6 7 8 go mod init mod名称go build 源码文件go mod download 下载依赖包go mod tidy 更新go .mod依赖记录go mod verify 校验go .mod文件或依赖包源码go mod why 查看依赖信息,为什么依赖go mod editgo mod vendor
1 2 3 4 5 6 7 go mod edit -module testgo mod edit -require github.com/hashicorp/golang-lru@0.5 .3 添加依赖包到modgo mod edit -fmt 格式化mod文件go mod edit -exclude 排除某个依赖包go mod edit -exclude github.com/hashicorp/golang-lru@0.5 .2 不能依赖此包 go mod edit -dropexclude github.com/hashicorp/golang-lru@0.5 .2 删除go .mod中的配置
1 2 3 4 5 module baihl go 1.15 exclude github.com/hashicorp/golang-lru 0.5 .2
1 go mod vendor 生成vendor目录存放依赖包
1.4 总结
1 2 3 go mod graphgo mod whygo list -m all
1 2 3 4 go get go buildgo mod edit requirego mod download
02 第二章 基本程序结构 2.1 退出返回值 与其他语言不同,Go中main函数不支持任何返回值 通过os.Exit来返回状态
1 2 3 4 5 6 func main () { if len (os.Args) > 1 { fmt.Println("Hello World" , os.Args[1 ]) } os.Exit(2 ) }
输出:
1 Process finished with exit code 2
2.2 获取命令行参数 与其他语言不同,main函数不支持传入参数 在程序中使用os.Args获取命令行参数
1 2 3 4 5 func main () { if len (os.Args) > 2 { fmt.Println("Hello World" , os.Args[1 ], os.Args[2 ]) } }
输出:
1 2 #go run hello_world.go bai haoliang Hello World bai haoliang
2.3 编写测试程序
源码文件以_test结尾:XXX_test.go
测试方法名以Test开头:func TestXXX(t *testing.T) {...}
1 2 3 4 5 6 7 8 9 10 11 func TestFibList (t *testing.T) { a := 1 b := 1 t.Log(a) for i := 0 ; i < 5 ; i++ { t.Log(" " , b) tmp := a a = b b = tmp + a } }
2.4 变量赋值
赋值可以进行自动类型推断
在一个赋值语句中可以对多个变量进行同时赋值
1 2 3 var b int = 1 a := 1 c, d := 1, 2
2.5 常量的定义 快速设置连续值
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package constant_testimport "testing" const ( Monday = 1 + iota Tuesday Wednesday ) const ( Readable = 1 << iota Writable Executable ) func TestConstantTry (t *testing.T) { t.Log(Monday, Tuesday) } func TestConstantTry1 (t *testing.T) { a := 1 t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable) }
2.5 常量的使用 const数值可作为各种类型使用
1 2 3 const filename = "abc.txt" const a, b = 3 , 4 var c int = int (math.Sqrt(a * a + b * b))
2.6 基本数据类型
Go语言不允许隐式类型转换
别名和原有类型也不能进行隐式转换
1 2 3 4 5 6 7 8 9 10 11 12 type MyInt int64 func TestImplicit (t *testing.T) { var a int32 = 1 var b int64 b = int64 (a) var c MyInt c = MyInt(b) t.Log(a, b, c) t.Log(math.MaxFloat32) }
math.MaxInt64
math.MaxFloat64
math.MaxUint32
不支持指针运算
string 是值类型,其默认的初始化为空字符串,而不是nil
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func TestPoint (t *testing.T) { a := 1 aPtr := &a 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)) }
1 mMap := new (map [int ]string )
2.6.1 指针/数组指针/指针数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func TestPoint () { var count int = 20 var countPoint *int countPoint = &count fmt.Println("count 的地址 : " , countPoint) } func TestPointArr () { a, b := 1 , 2 pointArr := [...]*int {&a, &b} fmt.Println("指针数组 pointArr : " , pointArr) arr := [...]int {3 , 4 , 5 } arrPoint := &arr fmt.Println("数组指针 arrPoint : " , arrPoint) } func main () { TestPoint() TestPointArr() }
输出
1 2 3 count 的地址 : 0xc000012090 指针数组 pointArr : [0xc000012098 0xc0000120b0] 数组指针 arrPoint : &[3 4 5]
2.6.2 指针使用 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 package mainimport ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64 ) { v.X = v.X * f v.Y = v.Y * f } func main () { v := Vertex{3 , 4 } v.Scale(10 ) fmt.Println(v.Abs()) }
2.6.2 结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 type Dog struct { ID int Name string Age int } func main () { dog := new (Dog) dog.ID = 1 dog.Name = "Tom" dog.Age = 12 fmt.Println(dog) }
2.7 算术运算符
注意: Go语言没有前置的++,–,如(++a)
2.8 比较运算符
可以使用==比较数组
相同维数且含有相同个数元素的数组才可以比较
每个元素都相同的才相等
1 2 3 4 5 6 7 8 9 func TestCompareArray (t *testing.T) { a := [...]int {1 , 2 , 3 , 4 } b := [...]int {1 , 3 , 2 , 4 } d := [...]int {1 , 2 , 3 , 4 } t.Log(a == b) t.Log(a == d) }
2.9 逻辑运算符
2.10 位运算符
Go语言多了一种位运算符 &^ (按位置零)
1 2 3 4 1&^0-- 1 1&^1-- 0 0&^1-- 0 0&^0-- 0
把指定位置零
示例:
1 2 3 4 5 6 7 8 9 10 11 12 const ( Readable = 1 << iota Writable Executable ) func TestBitClear (t *testing.T) { a := 7 a = a &^ Readable a = a &^ Executable t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable) }
输出
2.11 循环语句 与其他语言不同 只有for没有while语句
1 2 3 4 5 6 7 8 func TestWhileLoop (t *testing.T) { n := 0 for n < 5 { t.Log(n) n++ } }
2.12 条件语句 2.12.1 if 条件 与其他语言的差异
condition表达式结果必须为布尔值
支持变量赋值:
1 2 3 if var declaration; condition { }
2.12.2 switch条件
条件表达式不限制为常量或者整数
单个case中,可以出现多个结果,使用逗号分隔
1 2 3 4 5 6 7 8 9 10 11 12 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" ) } } }
与C语言等规则相反,Go语言不需要用break来明确退出一个case
可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if…else…的逻辑作用等同
1 2 3 4 5 6 7 8 9 10 11 12 func 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("unknow" ) } } }
fallthrough
使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
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 package mainimport "fmt" func main () { switch { case false : fmt.Println("1、case 条件语句为 false" ) fallthrough case true : fmt.Println("2、case 条件语句为 true" ) fallthrough case false : fmt.Println("3、case 条件语句为 false" ) fallthrough case true : fmt.Println("4、case 条件语句为 true" ) case false : fmt.Println("5、case 条件语句为 false" ) fallthrough default : fmt.Println("6、默认 case" ) } }
以上代码执行结果为:
1 2 3 2、case 条件语句为 true 3、case 条件语句为 false 4、case 条件语句为 true
switch 从第一个判断表达式为 true 的 case 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。
2.12.3 select语句 select语句只能用于通道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" func main () { ch4 := make (chan int , 1 ) for i := 0 ; i < 4 ; i++ { select { case e, ok := <-ch4: if !ok { fmt.Println("End." ) return } fmt.Println(e) close (ch4) default : fmt.Println("No Data!" ) ch4 <- 1 } } }
03 常用集合 3.0 概览
3.1 数组
1 2 3 4 5 6 7 8 9 func TestArrayTravel (t *testing.T) { arr3 := [...]int {1 , 3 , 4 , 5 } for i := 0 ; i < len (arr3); i++ { t.Log(arr3[i]) } for _, e := range arr3 { t.Log(e) } }
截取时是左闭右开区间 如下:
1 2 3 4 5 6 a := [...]int {1 ,2 ,3 ,4 ,5 } a[1 :2 ] a[1 :3 ] a[1 :len (a)] a[1 :] a[:3 ]
3.2 切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func TestSliceInit (t *testing.T) { var s0 []int 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)) t.Log(s2[0 ], s2[1 ], s2[2 ]) s2 = append (s2, 1 ) t.Log(s2[0 ], s2[1 ], s2[2 ], s2[3 ]) t.Log(len (s2), cap (s2)) }
其中len个元素会被初始化为零值,未初始化元素不可访问,会出现编译错误
可以通过如下程序看出len和cap的关系
1 2 3 4 5 6 7 func TestSliceGrowing (t *testing.T) { s := []int {} for i := 0 ; i < 10 ; i++ { s = append (s, i) t.Log(len (s), cap (s)) } }
可以发现随着len的长度空间不够,cap会以倍数增加。
1 2 3 4 5 6 7 8 9 10 slice_test.go:26: 1 1 slice_test.go:26: 2 2 slice_test.go:26: 3 4 slice_test.go:26: 4 4 slice_test.go:26: 5 8 slice_test.go:26: 6 8 slice_test.go:26: 7 8 slice_test.go:26: 8 8 slice_test.go:26: 9 16 slice_test.go:26: 10 16
切片的访问的数据可以与其他切片共享,如果被某个切片修改,则会影响其他切片
代码示例:
1 2 3 4 5 6 7 8 9 10 11 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) }
1 2 3 4 5 6 7 8 func TestSliceComparing (t *testing.T) { a := []int {1 , 2 , 3 , 4 } b := []int {1 , 2 , 3 , 4 } t.Log(a, b) }
总结:
数组可以直接比较,容量固定,可以截取访问
切片不可以直接比较,容量可以自动扩充,可以直接访问len长度的元素,越界编译出错
每一个切片都有一个底层数组,切片的底层数组什么时候会被替换?
确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。请记住,在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,append函数返回的是指向新底层数组的新切片。
3.3 Map
创建:make(map[string]int)
获取元素:m[key]
key不存在时,获取value类型的初值
用value, ok := m[key]来判断是否存在key
用delete删除一个key
使用range遍历key,或者遍历key,value对
不保证遍历顺序,如需顺序,需手动对key排序
使用len获得元素个数
map使用哈希表,必须可以比较相等
除了slice,map,function的内建类型都可以作为key
struct类型不包含上述字段,也可作为key
Map的定义
1 2 3 4 5 6 7 8 9 10 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)) }
map类型在使用make函数时只需要两个参数,第二个参数是map的cap长度
1 2 3 4 5 6 func TestTravelMap (t *testing.T) { m1 := map [int ]int {1 : 1 , 2 : 4 , 3 : 9 } for k, v := range m1 { t.Log(k, v) } }
1 2 3 4 5 6 7 8 9 10 11 12 func TestAccessNotExistingKey (t *testing.T) { m1 := map [int ]int {} t.Log(m1[1 ]) m1[2 ] = 0 t.Log(m1[2 ]) m1[3 ] = 0 if v, ok := m1[3 ]; ok { t.Logf("Key 3's value is %d" , v) } else { t.Log("key 3 is not existing." ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func TestMapForSet (t *testing.T) { mySet := map [int ]bool {} mySet[1 ] = true mySet[3 ] = true n := 3 if _, ok := mySet[n]; ok { t.Logf("%d is existing" , n) } else { t.Logf("%d is not existing" , n) } mySet[3 ] = true t.Log(len (mySet)) delete (mySet, 3 ) if _, ok := mySet[n]; ok { t.Logf("%d is existing" , n) } else { t.Logf("%d is not existing" , n) } }
1 2 3 4 5 6 7 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 )) }
统计字符串中每个单词的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "strings" "golang.org/x/tour/wc" ) func WordCount (s string ) map [string ]int { array_s := strings.Fields(s) fmt.Println(array_s) m := make (map [string ]int ) for _, v := range array_s { m[v] += 1 } return m } func main () { wc.Test(WordCount) }
04 字符串
string 是数据类型,不是引用或指针类型
string 是只读的byte slice,len函数可以获取所含的byte数
string 的byte数组可以存放任何数据
string底层是通过byte数组实现,中文字符在unicode下占两个字节,在utf-8编码下占3个字节,goland中默认编码就是utf-8 rune类型可以处理unicode字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func TestString (t *testing.T) { var s string t.Log(s) s = "hello" t.Log(len (s)) s = "\xE4\xBA\xBB\xFF" t.Log(s) t.Log(len (s)) s = "中" t.Log(len (s)) c := []rune (s) t.Log(len (c)) t.Logf("中 unicode %x" , c[0 ]) t.Logf("中 UTF8 %x" , s) }
4.1 参考资料 string常用功能包
strings包(https://golang.org/pkg/strings/)
strconv包 (https://golang.org/pkg/strconv/)
示例:
字符串切割
1 2 3 4 5 6 7 8 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, "-" )) }
字符串与整型转换1 2 3 4 5 6 7 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) } }
05 函数
5.1 可变参数 可变参数的函数定义
1 2 3 4 5 6 7 8 9 10 11 12 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 )) }
可以定义接收任何类型的可变参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func actionVariables3 (args ...interface {}) { for _,value := range args { switch value.(type ) { case int : fmt.Println(value,"int" ) case string : fmt.Println(value,"string" ) case float64 : fmt.Println(value,"float64" ) case bool : fmt.Println(value,"bool" ) default : fmt.Println(value,"unknow" ) } } } actionVariables3(1 ,"我是波哥" ,3.14 ,true )
可变参数,类似于切片,go语言里有很多,可变参数是go语言的特性。
5.2 defer的使用 1 2 3 4 5 6 7 func TestDefer (t *testing.T) { defer func () { fmt.Println("Clear resources." ) }() fmt.Println("Start" ) panic ("err" ) }
defer可以延迟执行,但会在panic之前执行
1 2 3 4 5 Start Clear resources. --- FAIL: TestDefer (0.00s) panic: err [recovered] panic: err
5.3 递归函数 1 2 3 4 5 6 7 8 9 10 11 12 - 1. 定义函数[fibonaci]实现 func fibonaci (index int64 ) int64 { if index == 1 || index == 2 { return 1 } else { return fibonaci(index-1 ) + fibonaci(index-2 ) } } - 2. 调用 fmt.Println(fibonaci(2 ))
5.4 匿名函数 没有名字的函数被称为匿名函数,当一个函数仅使用一次的时候,可定义为匿名函数
使用方式一
定义一个匿名函数实现两个数的加和,定义的时候并调用。
1 2 3 4 sumVariables := func (var1,var2 int ) int { return var1 + var2 }(1 ,2 ) fmt.Println(sumVariables)
使用方式二
1 2 3 4 5 func1 := func (var1,var2 int ) int { return var1 - var2 } fmt.Printf("func1调用的值=%d,func1=%p\n" ,func1(1 ,2 ),&func1)
输出
1 func1调用的值=-1,func1=0xc00008c000
5.5 闭包 首先它是一个函数,是函数与函数外部数据的引用(在函数内部引用了函数体外部的数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 func clousure () func (int64 ) int64 { var step int64 = 0 return func (_step int64 ) int64 { step += _step return step } } func2 := clousure() fmt.Println(func2(1 )) fmt.Println(func2(2 )) fmt.Println(func2(3 ))
输出:
07 错误处理 7.1 错误机制
没有异常机制
error类型实现了error接口
可以通过errors.New来快速创建错误实例
errors.New("n must be in the range [0,100]")
通过errors定义不同的错误变量,以便于判断错误类型
7.2 panic和recover
panic用于不可以恢复的错误
panic退出前会执行defer指定的内容
panic会打印出当前调用栈
os.Exit退出时不会调用defer指定的函数
os.Exit退出时不输出当前调用栈信息
1 2 3 4 5 6 7 8 9 10 11 12 func TestPanicVxExit (t *testing.T) { defer func () { if err := recover (); err != nil { fmt.Println("recovered from " , err) } }() fmt.Println("Start" ) panic (errors.New("Something wrong!" )) }
recover应该谨慎使用,因为recover可以捕捉panic错误,使程序不用退出,但如果捕捉后不做正确处理,可能形成僵尸程序,导致health check失效,还不如直接使用panic使程序退出,然后使用程序的守护进程重启程序。
7.3 异常处理示例 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 package mainimport ( "errors" "fmt" ) func receivePanic () { defer coverPanic() panic (errors.New("I am error" )) } func coverPanic () { message := recover () switch message.(type ) { case string : fmt.Println("string message : " , message) case error : fmt.Println("error message : " , message) default : fmt.Println("Unknown panic : " , message) } } func main () { receivePanic() }
09 并发编程 9.1 协程机制 Thread vs. Groutine
创建时默认的stack大小
JDK5以后Java Thread stack默认为1M
Groutine的stack初始化大小为2K
和KSE(Kernel Space Entity)的对应关系
Java Thread 是1:1
Groutine是 M:N
Groutine的机制是在用户空间由用户控制线程的切换,又因为KSE中M:N的关系,不会造成内核空间切换
9.2 共享内存并发机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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) }
输出:
通过加锁实现并发Groutine共享变量
9.3 CSP并发机制 Go中channel是有容量限制并且独立于处理Groutine 通过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 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 , 1 ) go func () { ret := service() fmt.Println("returned result." ) retCh <- ret fmt.Println("service exited." ) }() return retCh } func TestAsynService (t *testing.T) { retCh := AsyncService() otherTask() fmt.Println(<-retCh) time.Sleep(time.Second * 1 ) }
9.4 多路选择和超时控制 通过select实现多路选择
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func ServiceTime () chan string { retCh := make (chan string , 1 ) time.Sleep(time.Millisecond * 50 ) ret := "Done" retCh <- ret return retCh } func TestSelect (t *testing.T) { select { case <-time.After(time.Millisecond * 100 ): t.Error("time out" ) case ret := <-ServiceTime(): t.Log(ret) } }
可以通过case实现接收不同channel消息,然后执行相应case
9.5 Channel的关闭和广播
向关闭的channel发送数据会导致panic
读取关闭的channel,不会阻塞,返回是0
v, ok <- ch; ok为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 func dataProducer (ch chan int , wg *sync.WaitGroup) { go func () { for i := 0 ; i < 10 ; i++ { ch <- i } close (ch) wg.Done() }() } func dataReceiver (ch chan int , wg *sync.WaitGroup) { go func () { for { 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 ) dataReceiver(ch, &wg) wg.Wait() }
9.6 Context与任务取消
根Context:通过context.Background()创建
子Context:context.WithCancel(parentContext)创建
ctx, cancel := context.WithCancel(context.Background())
当前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 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 ) }
9.7 多协程间的通信 两个协程通过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 package mainimport ( "fmt" "runtime" "time" ) var ch1 chan int = make (chan int , 10 )var ch2 chan int = make (chan int , 10 )func Read () { for { select { case num := <-ch1: fmt.Println(num) case <-ch2: fmt.Println("ch2" ) } } } func Write () { ch1 <- 1 time.Sleep(time.Microsecond * 10 ) ch1 <- 2 time.Sleep(time.Microsecond * 10 ) ch2 <- 1 } func main () { fmt.Printf("cpu num = %d\n" , runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU() - 1 ) go Write() go Read() time.Sleep(time.Second * 2 ) }
9.8 多协程间的同步 系统提供的sync.waitgroup
Add(delta int)添加协程记录
Done() 移除协程记录
Wait() 同步等待所有记录的协程全部结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var WG sync.WaitGroupfunc TestSynWrite () { for i := 1 ; i < 10 ; i++ { WG.Add(1 ) } } func TestSynRead () { for i := 1 ; i < 10 ; i++ { time.Sleep(time.Second * 1 ) fmt.Printf("---> Done %d\n" , i) WG.Done() } } func main () { fmt.Printf("cpu num = %d\n" , runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU() - 1 ) TestSynWrite() go TestSynRead() WG.Wait() }
10 并发任务 10.1 多任务
使用sync.Once确保函数只执行一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var singleInstance *Singletonvar once sync.Oncefunc 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("%X\n" , unsafe.Pointer(obj)) wg.Done() }() } wg.Wait() }
会发现以上对象obj只创建一次,打印出的地址都是相同的
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 { numOfRunner := 10 ch := make (chan string , numOfRunner) for i := 0 ; i < numOfRunner; 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()) }
同时启动10个任务,只要任一任务完成,则channel中会有值,则立即返回,但为了防止其他Goroutine阻塞,则channel的大小和Goroutine的数量一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func AllResponse () string { numOfRunner := 10 ch := make (chan string , numOfRunner) for i := 0 ; i < numOfRunner; i++ { go func (i int ) { ret := runTask(i) ch <- ret }(i) } finalRet := "" for j := 0 ; j < numOfRunner; j++ { finalRet += <-ch + "\n" } return finalRet }
修改就是循环读取channel中所有数据
10.2 sync.Pool的使用
适合于通过复用,降低复杂对象的创建和GC代价
协程安全,会有锁的开销
生命周期受GC影响,不适合于做连接池等,需要自己管理生命周期的资源池化
11 测试 以上笔记使用的都是单元测试的框架,可以编写单元模块进行执行,练习。
11.1 内置单元测试框架
Fail,Error:该测试失败,该测试继续,其他测试继续执行
FailNow,Fatal:该测试失败,该测试中止,其他测试继续执行
以上是测试接口的差异,下边代码示例:
1 2 3 4 5 6 7 8 9 10 11 func TestErrorInCode (t *testing.T) { fmt.Println("Start" ) t.Error("Error" ) fmt.Println("End" ) } func TestFailInCode (t *testing.T) { fmt.Println("Start" ) t.Fatal("Error" ) fmt.Println("End" ) }
以上TestFailInCode执行到Fatal后终止,并不会输出End
使用以上命令行执行代码程序,可以展示出代码覆盖率。
断言模块 github.com/stretchr/testify/assert
使用断言时需要首先安装以上模块包
1 2 3 4 func TestAssert (t *testing.T) { assert.Equal(t, 1 , 1 ) }
以上测试两个参数相等,第二个参数是传入的值,第三个参数是期望值。
11.2 Benchmark Benchmark可以测试代码块的执行时间,以此测试代码性能。 使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 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() }
输出 可以看到限制代码块执行时间120ns/op
11.3 BDD in go 项目网站:https://github.com/smartystreets/goconvey
安装 go get -u github.com/smartystreets/goconvey/convey
启动WEB UI $GOPATH/bin/goconvey
12 反射编程 13 常见任务 13.1 json工具
内置json解析
1 2 3 4 5 6 7 import "encoding/json" func Unmarshal (data []byte , v interface {}) error func Marshal (v interface {}) ([]byte , error )
easyjson模块
参考:easyjson的使用 easyjson相对于内置json而言,解析更快,是通过代码生成,而非发射机制
需要首先安装easyjson模块
1 go get -u github.com/mailru/easyjson/...
使用命令生成代码: easyjson -all <结构定义>.go