Go语言基础
# Go语言 roadmap
# Go 语⾔言基础
- 基本程序结构
- 常⽤用集合
- 函数式编程
- ⾯面向对象编程
- 错误处理理
- 模块化及依赖管理
# 进阶与实战
- 并发编程模式
- 常⻅见并发任务
- 深⼊入测试
- 反射和 Unsafe
- 常⻅见架构模式的实现
- 性能调优
- ⾼高可⽤用性服务设计
# Go 语⾔言简介
# 软件开发的新挑战
- 多核硬件架构
- 超⼤大规模分布式计算集群
- Web 模式导致的前所未有的开发规模和更更新速度
# Go 的创始⼈人
- Rob Pike
- Unix 的早期开发者
- UTF-8 创始⼈人
- Ken Thompson
- Unix 的创始⼈人
- C 语⾔言创始⼈人
- 1983 年年获图灵奖
- Robert Griesemer
- Google V8 JS Engine 开发者
- Hot Spot 开发者
# Go 语⾔言发展
# 准备开始 Go 冒险之旅
下载安装 Go 语⾔言
https://golang.org/doc/install
https://golang.google.cn/dl/
安装 IDE
Atom: https://atom.io + Package: go-plus
2
3
4
5
6
# 编写第⼀一个 Go 程序
# 开发环境构建
GOPATH
- 在 1.8 版本前必须设置这个环境变量量
- 1.8 版本后(含 1.8)如果没有设置使⽤用默认值
在 Unix 上默认为 _$HOME/go
, 在 Windows 上默认为 _%USERPROFILE%/go_
在 Mac 上 GOPATH 可以通过修改 ~/.bash_profile
来设置
# 基本程序结构
package main //包,表明代码所在的模块(包)
import "fmt" //引⼊入代码依赖
//功能实现
func main() {
fmt.Println("Hello World!")
}
2
3
4
5
6
7
8
# 应⽤用程序⼊入⼝口
- 必须是 main 包:
package main
- 必须是 main ⽅方法:
func main()
- ⽂文件名不不⼀一定是
main.go
# 退出返回值
与其他主要编程语⾔言的差异
- Go 中
main
函数不不⽀支持任何返回值^ - 通过
os.Exit
来返回状态
# 获取命令⾏行行参数
与其他主要编程语⾔言的差异
main 函数不不⽀支持传⼊入参数
func main(arg []string)
在程序中直接通过
os.Args
获取命令⾏行行参数
# 变量量与常量量
# The master has failed more times than the beginner has tried.
# 编写测试程序
- 源码⽂文件以 _test 结尾:
xxx_test.go
- 测试⽅方法名以 Test 开头:
func TestXXX(t *testing.T) {...}
# 实现 Fibonacci 数列列
# 1, 1, 2, 3, 5, 8, 13, ....
# 变量量赋值
- 赋值可以进⾏行行⾃自动类型推断^
- 在⼀一个赋值语句句中可以对多个变量量进⾏行行同时赋值^
与其他主要编程语⾔言的差异
# 常量量定义
const (
Monday = iota + 1
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
const (
Open = 1 << iota
Close
Pending
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
快速设置连续值
与其他主要编程语⾔言的差异
# 数据类型
# 基本数据类型
bool
string
int
int8
int16
int32
int64
uint
uint8
uint16
uint32
uint64
uintptr
byte
// alias for uint8rune
// alias for int32,represents a Unicode code pointfloat32
float64
complex64
complex128
# 类型转化
- Go 语⾔言不不允许隐式类型转换
- 别名和原有类型也不不能进⾏行行隐式类型转换
与其他主要编程语⾔言的差异
# 类型的预定义值
math.MaxInt64
math.MaxFloat64
math.MaxUint32
# 指针类型
- 不不⽀支持指针运算
- string 是值类型,其默认的初始化值为空字符串串,⽽而不不是 nil
# 运算符
# 算术运算符
Go 语⾔言没有前置的 ++,- -,(++a)
运算符 描述 实例例
+ 相加 A + B 输出结果 30
- 相减 A - B 输出结果 -10
* 相乘 A * B 输出结果 200
/ 相除 B / A 输出结果 2
% 求余 B % A 输出结果 0
++ ⾃自增 A++ 输出结果 11
-- ⾃自减 A-- 输出结果 9
2
3
4
5
6
7
8
# ⽐比较运算符
运算符 描述 实例例
== 检查两个值是否相等,如果相等返回 True 否则返回 False。 (A == B) 为 False
!= 检查两个值是否不不相等,如果不不相等返回 True 否则返回 False。 (A != B) 为 True
> 检查左边值是否⼤大于右边值,如果是返回 True 否则返回 False。 (A > B) 为 False
< 检查左边值是否⼩小于右边值,如果是返回 True 否则返回 False。 (A < B) 为 True
>= 检查左边值是否⼤大于等于右边值,如果是返回 True 否则返回 False。 (A >= B) 为 False
<= 检查左边值是否⼩小于等于右边值,如果是返回 True 否则返回 False。 (A <= B) 为 True
2
3
4
5
6
7
- 相同维数且含有相同个数元素的数组才可以⽐比较
- 每个元素都相同的才相等
- ⽤用 == ⽐比较数组
# 逻辑运算符
运算符 描述 实例例
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 (A && B) 为 False
|| 逻辑 OR 运算符。 如果两边的操作数有⼀一个 True,则条件 True,否则为 False。 (A || B) 为 True
! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !(A && B) 为 True
2
3
4
# 位运算符
运算符 描述 实例例
& 按位与运算符各对应的⼆二进位相与。"&"是双⽬目运算符。^ 其功能是参与运算的两数 (A & B) 结果为 12, ⼆二进制为 0000 1100
| 按位或运算符各对应的⼆二进位相或"|"是双⽬目运算符。^ 其功能是参与运算的两数 (A | B) 结果为 61, ⼆二进制为 0011 1101
^ 按位异或运算符数各对应的⼆二进位相异或,当两对应的⼆二进位相异时,结果"^"是双⽬目运算符。^ 其功能是参与运算的两为 1 。(A ^ B) 结果为 49, ⼆二进制为 0011 0001
<< 左移运算符”<<"是双⽬目运算符。左移 n 位就是乘以 2 的 n 次⽅方。 其功能把"<<"左边的运算数的各⼆二进位全部左移若⼲干位,由"<<"右边的数指定移动的位数,⾼高位丢弃,低位补0 。A << 2 结果为 240 ,⼆二进制为 1111 0000
>> 右移运算符”>>"是双⽬目运算符。右移 n 位就是除以 2 的 n 次⽅方。 其功能是把">>"左边的运算数的各⼆二进位全部右移若⼲干位,">>"右边的数指定移动的位数。A >> 2 结果为 15 ,⼆二进制为 0000 1111
&^ 按位置零
1 &^ 0 -- 1
1 &^ 1 -- 0
0 &^ 1 -- 0
0 &^ 0 -- 0
2
3
4
5
6
7
8
9
10
11
12
13
# 位运算符
# 编写结构化程序
# 循环
for ( j := 7 ; j <= 9 ; j++ )
Go 语⾔言仅⽀支持循环关键字 for
# 代码示例例
n := 0
for n < 5 {
n++
fmt.Println(n)
}
// while 条件循环
while (n<5)
n := 0
for {
...
}
// ⽆无限循环
while (true)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# if 条件
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 condition-2 is true
} else {
// code to be executed if both condition1 and condition2 are false
}
2
3
4
5
6
7
8
9
10
11
12
13
- condition 表达式结果必须为布尔值
- ⽀支持变量量赋值:
if var declaration; condition {
// code to be executed if condition is true
}
2
3
# switch 条件
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.”)
//break
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
switch {
case 0 <= Num && Num <= 3:
fmt.Printf("0-3")
case 4 <= Num && Num <= 6:
fmt.Printf("4-6")
case 7 <= Num && Num <= 9:
fmt.Printf("7-9")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# switch条件
- 条件表达式不不限制为常量量或者整数;
- 单个 case 中,可以出现多个结果选项, 使⽤用逗号分隔;
- 与 C 语⾔言等规则相反,Go 语⾔言不不需要⽤用break来明确退出⼀一个 case;
- 可以不不设定 switch 之后的条件表达式,在此种情况下,整个 switch 结
构与多个 if...else... 的逻辑作⽤用等同
# 数组和切⽚片
# 数组的声明
var a [3]int //声明并初始化为默认零值
a[0] = 1
b := [3]int{1, 2, 3} //声明同时初始化
c := [2][2]int{{1, 2}, {3, 4}} //多维数组初始化
2
3
4
5
6
7
# 数组元素遍历
func TestTravelArray(t *testing.T) {
a := [...]int{1, 2, 3, 4, 5} //不不指定元素个数
for idx/*索引*/, elem/*元素*/ := range a {
fmt.Println(idx, elem)
}
}
2
3
4
5
6
# 数组截取
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
2
3
4
5
6
# 切⽚片内部结构
len: 元素的个数
cap: 内部数组的容量量
2
# 切⽚片声明
var s0 []int
s0 = append(s0, 1)
s := []int{}
s1 := []int{1, 2, 3}
s2 := make([]int, 2, 4)
/*[]type, len, cap
其中len个元素会被初始化为默认零值,未初始化元素不不可以访问
*/
2
3
4
5
6
7
8
切⽚片共享存储结构
# 数组 vs. 切⽚片
- 容量量是否可伸缩
- 是否可以进⾏行行⽐比较
# Map 基础
# Map 声明
m := map[string]int{"one": 1, "two": 2, "three": 3}
m1 := map[string]int{}
m1["one"] = 1
m2 := make(map[string]int, 10 /*Initial Capacity*/)
2
3
4
5
6
7
# Map 元素的访问
在访问的 Key 不不存在时,仍会返回零值,不不能通过返回 nil 来判断元素是否存在
if v, ok := m["four"]; ok {
t.Log("four", v)
} else {
t.Log("Not existing")
}
2
3
4
5
# Map 遍历
m := map[string]int{"one": 1, "two": 2, "three": 3}
for k, v := range m {
t.Log(k, v)
}
2
3
4
# Map 扩展
# Map 与⼯工⼚厂模式
- Map 的 value 可以是⼀一个⽅方法^
- 与 Go 的 Dock type 接⼝口⽅方式⼀一起,可以⽅方便便的实现单⼀一⽅方法对象的⼯工⼚厂模式
# 实现 Set
元素的唯⼀一性
基本操作
- 添加元素
- 判断元素是否存在
- 删除元素
- 元素个数
Go 的内置集合中没有 Set 实现, 可以 map[type]bool
# 字符串串与字符编码
# 字符串串
- string 是数据类型,不不是引⽤用或指针类型
- string 是只读的 byte slice,len 函数可以它所包含的 byte 数
- string 的 byte 数组可以存放任何数据
# Unicode UTF8
- Unicode 是⼀一种字符集(code point)
- UTF8 是 unicode 的存储实现 (转换为字节序列列的规则)
# 编码与存储
“中”
0x4E2D
0xE4B8AD
[0xE4,0xB8,0xAD]
字符
Unicode
UTF-8
string/[]byte
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 常⽤用字符串串函数
strings 包 (https://golang.org/pkg/strings/)
strconv 包 (https://golang.org/pkg/strconv/)
# 函数:⼀一等公⺠民
# 函数是⼀一等公⺠民
- 可以有多个返回值
- 所有参数都是值传递:slice,map,channel 会有传引⽤用的错觉
- 函数可以作为变量量的值
- 函数可以作为参数和返回值
与其他主要编程语⾔言的差异
# 学习函数式编程
# 函数:可变参数及 defer
# 可变参数
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
2
3
4
5
6
7
# defer 函数
func TestDefer(t *testing.T) {
defer func() {
t.Log("Clear resources")
}()
t.Log("Started")
panic("Fatal error”) //defer仍会执⾏行行
}
2
3
4
5
6
7
# ⾯面向对象编程
# Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.
https://golang.org/doc/faq
2
3
# 封装数据和⾏行行为
# 结构体定义
type Employee struct {
Id string
Name string
Age int
}
2
3
4
5
# 实例例创建及初始化
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"
2
3
4
5
6
# 行为(方法)定义
type Employee struct {
Id string
Name string
Age int
}
//第⼀一种定义⽅方式在实例例对应⽅方法被调⽤用时,实例例的成员会进⾏行行值复制
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义交互协议
# 接⼝口与依赖 Programmer.java
public interface Programmer {
String WriteCodes() ;
}
GoProgrammer.java
public class GoProgrammer implements Programmer {
@Override
public String WriteCodes() {
return "fmt.Println(\"Hello World\")";
}
}
Task.java
public class Task{
public static void main(String[] args) {
Programmer prog = new GoProgrammer();
String codes = prog.WriteCodes();
System.out.println(codes);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Duck Type 式接⼝口实现
type Programmer interface {
WriteHelloWorld() Code
}
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
2
3
4
5
6
7
8
9
10
# Go 接口
- 接⼝为⾮入侵性,实现不依赖于借⼝定义
- 所以接口的定义可以包含在接口使用者包内
# 自定义类型
type IntConvertionFn func(n int) int
type MyPoint int
# 扩展与复⽤
# 复合
Go 不支持继承,但可以通过复合的⽅式来复⽤
与其他主要编程语言的差异
# 匿名类型嵌⼊
- 不支持子类替换
- ⼦类并不是真正继承了父类的⽅方法
- ⽗类的定义的方法无法访问⼦类的数据和方法
- 它 不是继承 ,如果我们把“内部 struct ”看作⽗类,把“外部 struct” 看作⼦类
# 多态与空接⼝口
# 多态
type Programmer interface {
WriteHelloWorld() Code
}
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
type JavaProgrammer struct {
}
func (p *JavaProgrammer) WriteHelloWorld() Code {
return "System.out.Println(\"Hello World!\")"
}
func writeFirstProgram(p Programmer) {
fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 空接⼝口与断⾔言
- 空接⼝口可以表示任何类型
- 通过断⾔言来将空接⼝口转换为制定类型
- v, ok := p.(int) //ok=true 时为转换成功
# Go 接⼝口最佳实践
倾向于使⽤小的接口定义,很多接口只包含⼀一个⽅法。只依赖于必要功能的最⼩接⼝,较⼤的接口定义,可以由多个小接 口定义组合而成。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func StoreData(reader Reader) error {
...
}
2
3
4
5
6
7
8
9
10
11
12
13
# 编写好的错误处理
# Go 的错误机制
- 没有异常机制
- error 类型实现了了 error 接⼝
- 可以通过 errors.New 来快速创建错误实例例
type error interface {
Error() string
}
errors.New("n must be in the range [0,100]")
2
3
4
# 最佳实践
var LessThanTwoError error = errors.New("n must be greater than 2")
var GreaterThanHundredError error = errors.New("n must be less than 100")
func TestGetFibonacci(t *testing.T) {
var list []int
list, err := GetFibonacci(-10)
if err == LessThanTwoError {
t.Error("Need a larger number")
}
if err == GreaterThanHundredError {
t.Error("Need a larger number")
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
定义不不同的错误变量量,以便便于判断错误类型
# 最佳实践
# 及早失败,避免嵌套!
#
# panic 和 recover
# panic
panic
⽤用于不不可以恢复的错误panic
退出前会执⾏行行 defer 指定的内容
# panic
vs. os.Exit
os.Exit
退出时不不会调⽤用 defer 指定的函数os.Exit
退出时不不输出当前调⽤用栈信息
# recover
Java
try{
...
}catch(Throwable t){
}
2
3
4
5
# recover
defer func() {
if err := recover(); err != nil {
//恢复错误
}
}()
2
3
4
5
# 最常见的”错误恢复“
defer func() {
if err := recover(); err != nil {
log.Error(“recovered panic”,err)
}
}()
2
3
4
5
# 当⼼心! recover 成为恶魔
- 形成僵⼫尸服务进程,导致 health check 失效。
- “Let it Crash!” 往往是我们恢复不确定性错误的最好⽅法。
# 构建可复用模块:包
# package
基本复用模块单元以⾸字⺟大写来表明可被包外代码访问
代码的
package
可以和所在的目录不一致同⼀目录⾥的
Go
代码的package
要保持一致
# package
- 通过
go get
来获取远程依赖go get -u
强制从⽹络更新远程依赖
- 注意代码在 GitHub 上的组织形式,以适应 go get
- 直接以代码路径开始,不要有 src
- 示例例: https://github.com/easierway/concurrent_map
# init ⽅方法
- 在 main 被执⾏前,所有依赖的 package 的 init 方法都会被执⾏。
- 不同包的 init 函数按照包导入的依赖关系决定执行顺序。
- 每个包可以有多个 init 函数。
- 包的每个源⽂文件也可以有多个 init 函数,这点⽐比较特殊。
# 依赖管理理
# Go 未解决的依赖问题
- 同⼀一环境下,不不同项⽬目使⽤用同⼀包的不不同版本
- ⽆无法管理理对包的特定版本的依赖
# 协程机制
# Thead vs. Groutine
- 创建时默认的 stack 的⼤大⼩小
- JDK5 以后 Java Thread stack 默认为1M^
- Groutine 的 Stack 初始化⼤大⼩小为2K^
- 和 KSE (Kernel Space Entity) 的对应关系
- Java Thread 是 1:1^
- Groutine 是 M:N
M System Thread
P Processor
G Goroutine
2
3
# 共享内存并发机制
# Lock、Mutex、RWLock
package sync
Lock lock = ...;
lock.lock();
2
3
# WaitGroup
var wg sync.WaitGroup
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
defer func() {
wg.Done()
}()
...
}()
}
wg.Wait()
2
3
4
5
6
7
8
9
10
11
# CSP 并发机制
# CSP vs. Actor
- 和Actor的直接通讯不不同,CSP模式则是通过Channel进⾏行行通讯的,更更松耦合⼀一 些。
- Go中channel是有容量量限制并且独⽴立于处理理Groutine,⽽而如Erlang,Actor模式 中的mailbox容量量是⽆无限的,接收进程也总是被动地处理理消息。
# Channel异步返回
private static FutureTask<String> service() {
FutureTask<String> task = new FutureTask<String>(()->"Do
something");
new Thread(task).start();
return task;
}
FutureTask<String> ret = service();
System.out.println("Do something else”);
System.out.println(ret.get());
2
3
4
5
6
7
8
9
10
# 多路路选择和超时控制
# select
select {
case ret := <-retCh:
t.Logf("result %s", ret)
case <-time.After(time.Second * 1):
t.Error("time out")
}
select {
case ret := <-retCh1:
t.Logf("result %s", ret)
case ret :=<-retCh2:
t.Logf("result %s", ret)
default:
t.Error(“No one returned”)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# channel 的关闭和⼴广播
# channel 的关闭
- 向关闭的
channel
发送数据,会导致panic
v, ok <-ch; ok
为 bool 值,true
表示正常接受,false
表示通道关闭- 所有的
channel
接收者都会在channel
关闭时,⽴立刻从阻塞等待中返回且上述 ok 值为 false。这个⼴广播机制常被利利⽤用,进⾏行行向多个订阅者同时发送信号。如:退出信号。
# 任务的取消
# 获取取消通知
func isCancelled(cancelChan chan struct{}) bool {
select {
case <-cancelChan:
return true
default:
return false
}
}
2
3
4
5
6
7
8
# 发送取消消息
func cancel_1(cancelChan chan struct{}) {
cancelChan <- struct{}{}
}
2
3
# 通过关闭 Channel 取消
func cancel_2(cancelChan chan struct{}) {
close(cancelChan)
}
2
3
# Context 与任务取消
# Context
- 根 Context:通过
context.Background ()
创建 - 子 Context:
context.WithCancel(parentContext)
创建 ctx, cancel := context.WithCancel(context.Background())
- 当前 Context 被取消时,基于他的⼦ context 都会被取消
- 接收取消通知
<-ctx.Done()
# 常⻅见并发任务
# 单例例模式 (懒汉式,线程安全)
public class Singleton {
private static Singleton INSTANCE=null;
private Singleton(){}
public static Singleton getIntance(){
if(INSTANCE==null){
synchronized (Singleton.class){
if(INSTANCE==null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单例例模式 (懒汉式,线程安全)
var once sync.Once
var obj *SingletonObj
func GetSingletonObj() *SingletonObj {
once.Do(func() {
fmt.Println("Create Singleton obj.")
obj = &SingletonObj{}
})
return obj
}
2
3
4
5
6
7
8
9
# 对象池
使用buffered channel 实现对象池。
# 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)
2
3
4
5
6
7
8
# sync.Pool 对象的⽣生命周期
- GC 会清除 sync.pool 缓存的对象
- 对象的缓存有效期为下一次 GC 之前
# sync.Pool 总结
- 适合于通过复⽤用,降低复杂对象的创建和 GC 代价
- 协程安全,会有锁的开销
- 生命周期受 GC 影响,不适合于做连接池等,需⾃己管理生命周期的资源的池化
# 测试
# 单元测试
# 内置单元测试框架
- Fail, Error: 该测试失败,该测试继续,其他测试继续执⾏
- FailNow, Fatal: 该测试失败,该测试中止,其他测试继续执⾏
# 内置单元测试框架
- 代码覆盖率
go test -v - cover
- 断⾔ https://github.com/stretchr/testify
# Benchmark
# Benchmark
func BenchmarkConcatStringByAdd(b *testing.B) {
//与性能测试⽆无关的代码
b.ResetTimer()
for i := 0; i < b.N; i++ {
//测试代码
}
b.StopTimer()
//与性能测试⽆无关的代码
}
2
3
4
5
6
7
8
9
# Benchmark
go test -bench=. -benchmem
-bench=<相关benchmark测试>
Windows 下使⽤用 go test 命令⾏行行时,-bench=.应写为-bench="."
2
3
4