下载安装Go语言
https://golang.org/doc/install
安装IDE
Atom:https://atom.io + Package:go-plus
开发环境构建
GOPATH
-
1.8版本之前必须设置这个环境变量
-
1.8版本之后(含1.8),如果没有设置使用默认值
在Unix上默认为$HOME/go,在Windows上,默认为%USERPROFILE%/go
Mac上GOPATH可以通过修改~./bash_profile来设置
-
可以通过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和路径名不一定要求保持一致
应用程序入口
- 必须是main包:package main
- 必须是main方法: func main()
- 文件名不一定是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
编写测试程序
- 源码文件以_test结尾:xxx_test.go
- 测试方法名以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位
类型转化
与其他主要编程语言的差异
- Go语言不允许隐式类型转换
- 别名和原有类型越不能进行隐式类型转换
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)
}
类型的预定义值
- math.MaxInt64
- math.MaxFloat64
- math.MaxUint32
指针类型
与其他主要编程语言的差异
- 不支持指针运算
- 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
}
与其他主要编程语言的差异
- Condition 表达式结果必须为布尔值
- 支持变量赋值
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")
}
与其他主要编程语言的差异
- 条件表达式不限制为常量或者整数
- 单个case中,可以出现多个结果选项,使用逗号分隔
- Go语言不需要使用break来明确退出一个case
- 可以不设定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切片
-
容量是否可缩容
数组不可缩容,切片可以缩容
-
是否可进行比较
数组可以比较,切片不能进行比较
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
- 元素的唯一性
- 基本操作
- 添加元素
- 判断元素是否存在
- 删除元素
- 元素个数
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)
}
}
字符串
与其他主要编程语言的差异
- string是数据类型,不是引用或者指针类型
- string是只读的byte slice,len函数可以返回它所包含的byte数
- string的byte数组可以存放任何数据
Unicode UTF-8
- Unicode是一种字符集(code point)
- 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] |
常用字符串函数
- strings包(https://golang.org/pkg/strings)
- 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)
}
}
函数是一等公民
与其他主要编程语言的差异
- 可以有多个返回值
- 所有参数都是值传递:slice,map,channel会有引用传递的错觉
- 函数可以作为变量的值
- 函数可以作为参数和返回值
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())
}
与其他主要编程语言的差异
- 接口为非入侵,实现不依赖于接口定义
- 所以接口的定义可以包含在接口使用者包内
接口变量
自定义类型
- type IntConvertionFn func(n int) int
- 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
空接口与断言
-
空接口可以表示任何类型
-
通过断言来将空接口转换为指定类型
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的错误机制
与其他主要编程语言的差异
- 没有异常机制
- error类型实现了error接口
- 可以通过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
-
基本复用模块单元
以首字母大写来表明可被包外代码访问
-
代码的package可以和所在的目录不一致
-
同一目录里的Go代码的package要保持一致
需要配置GOPATH,go会去从GOPATH的src下找路径
package
- 通过go get 来获取远程依赖
- go get -u 强制从远程网络更新远程依赖
- 注意代码在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
依赖管理
未解决的依赖问题
- 同一环境下,不同项目使用同一包的不同版本
- 无法管理对包的特定版本的依赖
vendor路径
随着Go 1.5release版本的发布,vendor目录被添加到除了GOPATH和GOROOT之外的依赖目录查找解决方案。在Go1.6之前,需要手动设置环境变量
查找依赖包了路径的解决方案如下
- 当前包下的vendor目录
- 向上级目录查找,直到找到src下的vendor目录
- 在GOPATH下面查找依赖包
- 在GOROOT目录下查找
常用的依赖管理工具
godep https://github.com/tools/godep
glide https://github.com/Masterminds/glide
dep https://github.com/golang/dep
协程
Thread Vs Groutine
- 创建时默认的stack(栈)的大小
- JDK5以后Java Thread stack默认为1M
- Groutine的Stack初始化大小为2K
- 和KSE(kernel Space Entiry)的对应关系
- Java Thread 是1:1
- Groutine是M:N
Kernel entity是有cpu直接进行调度,调度效率很高,如果线程对象发生切换,会涉及到内核对象的切换,消耗会很高。
如果多个线程对象对应一个内核对象,那么他们之间的消耗就会小很多
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
依赖于一个通道完成两个通信实体之间的协调
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实现对象池
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对象获取
- 尝试从私有对象获取
- 私有对象不存在,尝试从当前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
-
代码覆盖率
go test -v -cover
-
断言
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))