0 参考资料 GO开源项目收集 :https://github.com/avelino/awesome-go 
01 第一章 Go语言简介 1.1 工作区和GOPATH 工作区是放置Go源码文件的目录
用于存放归档文件(名称以.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函数不支持任何返回值
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函数不支持传入参数
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) float64  {	return  math.Sqrt(v.X*v.X + v.Y*v.Y) } func  (v *Vertex) 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 循环语句 与其他语言不同
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
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
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 () 	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
使用以上命令行执行代码程序,可以展示出代码覆盖率。
断言模块
使用断言时需要首先安装以上模块包
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() } 
输出
11.3 BDD in go 项目网站:https://github.com/smartystreets/goconvey 
安装
启动WEB UI
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模块
1 go get -u github.com/mailru/easyjson/... 
使用命令生成代码: