编写第一个 Go 程序

下载和安装

最新版:https://go.dev/doc/install

以往版本:https://golang.google.cn/dl/

🌟项目名:go_learning

基本程序结构

src\ch1\main

hello_world.go

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
fmt.Println("Hello World")
}

获取退出返回值:os.Exit

获取命名行参数:os.Args

测试程序要求:

  • 测试文件:xxx_test.go
  • 测试方法:func TestXXX(t *testing.T){...}

src\ch2\test

1⃣first_test.go

1
2
3
4
5
6
7
package try_test

import "testing"

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

=== RUN TestFirstTry
h:\go_learning\src\ch2\test\first_test.go:6: My first try!
— PASS: TestFirstTry (0.00s)
PASS
ok cs.liangjiajia.com/ch2/test (cached)

2⃣second_test.go

实现 Fibonacci\text {Fibonacci} 数列:1122335588,1313\cdots

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package try_test

import (
"fmt"
"testing"
)

func TestSecond(t *testing.T) {
a := 1
b := 1
fmt.Print(a, " ")
for i := 0; i < 10; i++ {
fmt.Print(" ", b)
tmp := a + b
a = b
b = tmp
}
fmt.Println()
}

=== RUN TestSecond
1 1 2 3 5 8 13 21 34 55 89
— PASS: TestFibonacci (0.00s)
PASS
ok cs.liangjiajia.com/ch2/test 1.428s

📌其他变量命名方法:

  • var x int = a
  • var (x int = a y int = b ...)
  • var (x = a y = b ...)
  • var a int + a = 1

3⃣third_test.go

实现两数交换:1 21 \ \leftrightarrow 2

1
2
3
4
5
6
7
8
9
10
11
package try_test

import "testing"

func TestThirdTry(t *testing.T) {
a := 1
b := 2
t.Log(a, b)
a, b = b, a
t.Log(a, b)
}

=== RUN TestThirdTry
h:\go_learning\src\ch2\test\third_test.go:8: 1 2
h:\go_learning\src\ch2\test\third_test.go:10: 2 1
— PASS: TestThirdTry (0.00s)
PASS
ok cs.liangjiajia.com/ch2/test 1.492s

4⃣fourth_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package try_test

import "testing"

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

func TestFourth(t *testing.T) {
t.Log(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
}

=== RUN TestFourth
h:\go_learning\src\ch2\test\fourth_test.go:16: 0 1 2 3 4 5 6
— PASS: TestFourth (0.00s)
PASS
ok cs.liangjiajia.com/ch2/test 1.474s

5⃣​fifth_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package try_test

import "testing"

const (
Readable = 1 << iota
Writable
Executable
)

func TestFifth(t *testing.T) {
t.Log(Readable, Writable, Executable)
flag1 := 7 // 0111
flag2 := 3 // 0011
t.Log(flag1&Readable == Readable, flag1&Writable == Writable, flag1&Executable == Executable)
t.Log(flag2&Readable == Readable, flag2&Writable == Writable, flag2&Executable == Executable)
}
  • Readable:1 << 0,即 1100010001
  • Writable:1 << 1,即 2200100010
  • Executable:1 << 2,即 4401000100

=== RUN TestFifth
h:\go_learning\src\ch2\test\fifth_test.go:12: 1 2 4
h:\go_learning\src\ch2\test\fifth_test.go:15: true true true
h:\go_learning\src\ch2\test\fifth_test.go:16: true true false
— PASS: TestFifth (0.00s)
PASS
ok cs.liangjiajia.com/ch2/test 1.418s

基本数据类型

src\ch3\type

  • 布尔型bool

  • 字符串类型string

  • 数字类型

    • int(32位或64位)、int8int16int32int64

    • uint(32位或64位)、uint8uint16uint32uint64unitptr(无符号整型,用于存放一个指针)

  • 浮点型

    • float32float64
    • complex64complex128
  • byte:类似unit8

  • rune:类似int32

Go\text {Go} 语言不支持隐式类型转换

type_test.go

1
2
3
4
5
6
func TestImplicit(t *testing.T) {
var a int32 = 1
var b int64
b = int64(a)
t.Log(a, b)
}

=== RUN TestImplicit
h:\go_learning\src\ch3\type\type_test.go:11: 1 1
— PASS: TestImplicit (0.00s)
PASS
ok cs.liangjiajia.com/ch3/type 1.431s

不支持指针运算,string类型初始化为空字符串。

1
2
3
4
5
func TestPoint(t *testing.T) {
a := 1
aPtr := &a
t.Logf("%T %T", a, aPtr)
}

=== RUN TestPoint
h:\go_learning\src\ch3\type\type_test.go:17: int *int
— PASS: TestPoint (0.00s)
PASS
ok cs.liangjiajia.com/ch3/type 1.504s

1
2
3
4
5
func TestString(t *testing.T) {
var s string
t.Log("*" + s + "*")
t.Log(len(s))
}

=== RUN TestString
h:\go_learning\src\ch3\type\type_test.go:22: **
h:\go_learning\src\ch3\type\type_test.go:23: 0
— PASS: TestString (0.00s)
PASS
ok cs.liangjiajia.com/ch3/type 1.600s

运算符

src\ch4\operator

算术运算符

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 求余
++ 自增
-- 自减

比较运算符

运算符 描述
== 检查两个值是否相等
!= 检查两个值是否不相等
> 检查左边值是否大于右边值
< 检查左边值是否小于右边值
>= 检查左边值是否大于等于右边值
<= 检查左边值是否小于等于右边值

==比较数组,维度相同,对应元素相同

operator_test.go

1
2
3
4
5
6
7
8
9
func TestCompareArray(t *testing.T) {
a := [...]int{1, 2, 3, 4}
b := [...]int{1, 0, 3, 4}
c := [...]int{1, 2, 3, 4}
// d := [...]int{1, 2, 3, 4, 5}
t.Log(a == b)
t.Log(a == c)
// t.Log(a == d) // invalid operation: a == d (mismatched types [4]int and [5]int)
}

=== RUN TestCompareArray
h:\go_learning\src\ch4\operator\operator_test.go:10: false
h:\go_learning\src\ch4\operator\operator_test.go:11: true
— PASS: TestCompareArray (0.00s)
PASS
ok cs.liangjiajia.com/ch4/operator 1.485s

逻辑运算符

运算符 描述
&& 逻辑 AND\text {AND} 运算符
|| 逻辑 OR\text {OR} 运算符
! 逻辑 NOT\text {NOT} 运算符

位运算符

运算符 描述
& 按位与运算符
| 按位或运算符
^ 按位异或运算符
<< 左移运算符
>> 右移运算符

&^:按位置零

  • 1 &^ 0 \rightarrow 1
  • 0 &^ 0 \rightarrow 0
  • 1 &^ 1 \rightarrow 0
  • 0 &^ 1 \rightarrow 0

operator_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const (
Readable = 1 << iota
Writable
Executable
)

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

=== RUN TestBitClear
h:\go_learning\src\ch4\operator\operator_test.go:22: 1 2 4
h:\go_learning\src\ch4\operator\operator_test.go:24: true true true
h:\go_learning\src\ch4\operator\operator_test.go:26: false true true
h:\go_learning\src\ch4\operator\operator_test.go:28: false true false
— PASS: TestBitClear (0.00s)
PASS
ok

条件语句

if条件语句:

1
2
3
if condition { 
/* Execute when the boolean condition is true */
}

if...else...条件语句:

1
2
3
4
5
if condition {
/* Execute when the boolean condition is true */
} else {
/* Execute when the boolean condition is false */
}

switch条件语句:

1
2
3
4
5
6
7
8
9
10
11
switch condition {
case value1,...:
/* Execute when condition equals value1,.. */
case value2,...:
/* Execute when condition equals value2,... */
case value3,...:
/* Execute when condition equals value3,... */
...
default:
/* Execute when none of the above cases match */
}

select条件语句:

1
2
3
4
5
6
7
8
9
10
11
12
select {
case dataFromChannel1 := <-channel1:
/* Execute when data is received from channel1 */
case dataFromChannel2 := <-channel2:
/* Execute when data is received from channel2 */
case channel1 <- newData:
/* Execute when newData is sent to channel1 */
case channel2 <- newData:
/* Execute when newData is sent to channel2 */
default:
/* Execute when no channel operation is ready */
}

src\ch5\condition</i>

condition_test.go

1
2
3
4
5
6
7
8
9
10
// if
func TestIfCondition(t *testing.T) {
for i := 0; i < 5; i++ {
if i%2 == 0 {
t.Log("Even")
} else {
t.Log("Odd")
}
}
}

=== RUN TestIfCondition
h:\go_learning\src\ch5\condition\condition_test.go:9: Even
h:\go_learning\src\ch5\condition\condition_test.go:11: Odd
h:\go_learning\src\ch5\condition\condition_test.go:9: Even
h:\go_learning\src\ch5\condition\condition_test.go:11: Odd
h:\go_learning\src\ch5\condition\condition_test.go:9: Even
— PASS: TestIfCondition (0.00s)
PASS
ok cs.liangjiajia.com/ch5/condition (cached)

condition_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// if-else
func TestIfElseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
if i%2 == 0 {
t.Log("Even")
}
if i%2 == 1 {
t.Log("Odd")
}
if i%2 != 1 && i%2 != 0 {
t.Log("Unkonw")
}
}
}

=== RUN TestIfElseCondition
h:\go_learning\src\ch5\condition\condition_test.go:20: Even
h:\go_learning\src\ch5\condition\condition_test.go:23: Odd
h:\go_learning\src\ch5\condition\condition_test.go:20: Even
h:\go_learning\src\ch5\condition\condition_test.go:23: Odd
h:\go_learning\src\ch5\condition\condition_test.go:20: Even
— PASS: TestIfElseCondition (0.00s)
PASS
ok cs.liangjiajia.com/ch5/condition (cached)

condition_test.go

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("Unkonw")
}
}
}

=== RUN TestSwitchCaseCondition
h:\go_learning\src\ch5\condition\condition_test.go:36: Even
h:\go_learning\src\ch5\condition\condition_test.go:38: Odd
h:\go_learning\src\ch5\condition\condition_test.go:36: Even
h:\go_learning\src\ch5\condition\condition_test.go:38: Odd
h:\go_learning\src\ch5\condition\condition_test.go:36: Even
— PASS: TestSwitchCaseCondition (0.00s)
PASS
ok cs.liangjiajia.com/ch5/condition 0.038s

condition_test.go

1
2
3
4
5
6
7
8
9
10
11
12
func TestSwitchMultiCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2, 4:
t.Log("Even")
case 1, 3:
t.Log("Odd")
default:
t.Log("Unkonw")
}
}
}

=== RUN TestSwitchMultiCaseCondition
h:\go_learning\src\ch5\condition\condition_test.go:49: Even
h:\go_learning\src\ch5\condition\condition_test.go:51: Odd
h:\go_learning\src\ch5\condition\condition_test.go:49: Even
h:\go_learning\src\ch5\condition\condition_test.go:51: Odd
h:\go_learning\src\ch5\condition\condition_test.go:49: Even
— PASS: TestSwitchMultiCaseCondition (0.00s)
PASS
ok cs.liangjiajia.com/ch5/condition 0.039s

循环语句

for循环

1
2
3
for init; condition; post {
/* Execute the loop body while the condition is true */
}

while循环

1
2
3
for condition { 
/* Execute the loop body while the condition is true */
}
  • init: 一般为赋值表达式,给控制变量赋初值;
  • condition: 关系表达式或逻辑表达式,循环控制条件;
  • post: 一般为赋值表达式,给控制变量增量或减量。

src\ch5\loop

loop_test.go

1
2
3
4
5
6
7
func TestWhileLoop(t *testing.T) {
i := 0
for i < 5 {
t.Log(i)
i++
}
}

=== RUN TestWhileLoop
h:\go_learning\src\ch5\loop\loop_test.go:8: 0
h:\go_learning\src\ch5\loop\loop_test.go:8: 1
h:\go_learning\src\ch5\loop\loop_test.go:8: 2
h:\go_learning\src\ch5\loop\loop_test.go:8: 3
h:\go_learning\src\ch5\loop\loop_test.go:8: 4
— PASS: TestWhileLoop (0.00s)
PASS
ok cs.liangjiajia.com/ch5/loop 1.522s

loop_test.go

1
2
3
4
5
func TestForLoop(t *testing.T) {
for i := 0; i < 5; i++ {
t.Log(i)
}
}

=== RUN TestForLoop
h:\go_learning\src\ch5\loop\loop_test.go:15: 0
h:\go_learning\src\ch5\loop\loop_test.go:15: 1
h:\go_learning\src\ch5\loop\loop_test.go:15: 2
h:\go_learning\src\ch5\loop\loop_test.go:15: 3
h:\go_learning\src\ch5\loop\loop_test.go:15: 4
— PASS: TestForLoop (0.00s)
PASS
ok cs.liangjiajia.com/ch5/loop 0.039s

常用集合

数组

数组声明

1
var arrayName [size]dataType
1
2
3
4
5
// 数组声明
func TestArrayDeclare(t *testing.T) {
var arr [3]int
t.Log(arr)
}

=== RUN TestArrayDeclare
h:\go_learning\src\ch6\array\array_test.go:8: [0 0 0]
— PASS: TestArrayDeclare (0.00s)
PASS
ok cs.liangjiajia.com/ch6/array 1.567s

数组初始化

  • 指定数组长度
1
var arrayName [size]dataType{element_1,element_2,...,element_size-1}
1
arrayName := [size]dataType{element_1,element_2,...,element_size-1}
  • 不指定数组长度,自动推断
1
var arrayName [...]dataType{element_1,element_2,...}
1
arrayName := [...]dataType{element_1,element_2,...}

src\ch6\array

array_test.go

1
2
3
4
5
6
7
8
// 数组初始化
func TestArrayInit(t *testing.T) {
arr1 := [3]int{1, 2, 3} // var arr1 = [3]int{1, 2, 3}
t.Log(arr1)

arr2 := [...]int{1, 2, 3} // var arr2 =[...]int{1,2,3}
t.Log(arr2)
}

=== RUN TestArrayInit
h:\go_learning\src\ch6\array\array_test.go:14: [1 2 3]
h:\go_learning\src\ch6\array\array_test.go:17: [1 2 3]
— PASS: TestArrayInit (0.00s)
PASS
ok cs.liangjiajia.com/ch6/array 0.040s

关于数组遍历,数组切片,数组扩容

array_test.go

1
2
3
4
5
6
7
8
9
10
// 数组遍历
func TestArrayTravel(t *testing.T) {
arr := [3]int{1, 2, 3}
for i := 0; i < len(arr); i++ {
t.Log(arr[i])
}
for _, e := range arr {
t.Log(e)
}
}

=== RUN TestArrayTravel
h:\go_learning\src\ch6\array\array_test.go:24: 1
h:\go_learning\src\ch6\array\array_test.go:24: 2
h:\go_learning\src\ch6\array\array_test.go:24: 3
h:\go_learning\src\ch6\array\array_test.go:27: 1
h:\go_learning\src\ch6\array\array_test.go:27: 2
h:\go_learning\src\ch6\array\array_test.go:27: 3
— PASS: TestArrayTravel (0.00s)
PASS
ok cs.liangjiajia.com/ch6/array 0.039s

array_test.go

1
2
3
4
5
6
7
// 数组切片
func TestArraySection(t *testing.T) {
arr := [5]int{1, 2, 3, 4, 5}
arr_sec1 := arr[:2]
arr_sec2 := arr[3:]
t.Log(arr_sec1, arr_sec2)
}

=== RUN TestArraySection
h:\go_learning\src\ch6\array\array_test.go:36: [1 2] [4 5]
— PASS: TestArraySection (0.00s)
PASS
ok cs.liangjiajia.com/ch6/array 0.038s

array_test.go

1
2
3
4
5
6
7
8
// 数组扩容
func TestArraySliceGrowing(t *testing.T) {
arr := []int{}
for i := 0; i < 10; i++ {
arr = append(arr, i)
t.Log(len(arr), cap(arr))
}
}

=== RUN TestArraySliceGrowing
h:\go_learning\src\ch6\array\array_test.go:44: 1 1
h:\go_learning\src\ch6\array\array_test.go:44: 2 2
h:\go_learning\src\ch6\array\array_test.go:44: 3 4
h:\go_learning\src\ch6\array\array_test.go:44: 4 4
h:\go_learning\src\ch6\array\array_test.go:44: 5 8
h:\go_learning\src\ch6\array\array_test.go:44: 6 8
h:\go_learning\src\ch6\array\array_test.go:44: 7 8
h:\go_learning\src\ch6\array\array_test.go:44: 8 8
h:\go_learning\src\ch6\array\array_test.go:44: 9 16
h:\go_learning\src\ch6\array\array_test.go:44: 10 16
— PASS: TestArraySliceGrowing (0.00s)
PASS
ok cs.liangjiajia.com/ch6/array 0.040s

array_test.go数组切片共享原数组的存储空间

1
2
3
4
5
6
7
8
// 数组切片共享内存空间
func TestSliceShareMemory(t *testing.T) {
year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
summer := year[5:8]
t.Log(summer, len(summer), cap(summer))
summer[0] = "Unknow"
t.Log(year)
}

=== RUN TestSliceShareMemory
h:\go_learning\src\ch6\array\array_test.go:52: [Jun Jul Aug] 3 7
h:\go_learning\src\ch6\array\array_test.go:54: [Jan Feb Mar Apr May Unknow Jul Aug Sep Oct Nov Dec]
— PASS: TestSliceShareMemory (0.00s)
PASS
ok cs.liangjiajia.com/ch6/array 1.499s

Map

src\ch6\map

map_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Map初始化
func TestMapInit(t *testing.T) {
m1 := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
t.Log(m1)

m2 := make(map[string]int)
m2["red"] = 1
m2["yellow"] = 2
m2["orange"] = 3
t.Log(m2)
}

=== RUN TestMapInit
h:\go_learning\src\ch6\map\map_test.go:11: map[apple:1 banana:2 orange:3]
h:\go_learning\src\ch6\map\map_test.go:17: map[orange:3 red:1 yellow:2]
— PASS: TestMapInit (0.00s)
PASS
ok cs.liangjiajia.com/ch6/map 1.487s

键不存在

map_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
// Map:Key不存在 Value 被初始化为零值,区分 Value 本身是零值
func TestAccessNotExistingKey(t *testing.T) {
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
if v, ok := m["four"]; ok {
t.Log("four", v)
} else {
t.Log("Not existing!")
}
}

=== RUN TestAccessNotExistingKey
h:\go_learning\src\ch6\map\map_test.go:29: Not existing!
— PASS: TestAccessNotExistingKey (0.00s)
PASS
ok cs.liangjiajia.com/ch6/map 0.036s

Map遍历

map_test.go

1
2
3
4
5
6
7
8
9
10
11
// Map遍历
func TestMapTravel(t *testing.T) {
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
for k, v := range m {
t.Log(k, v)
}
}

=== RUN TestMapTravel
h:\go_learning\src\ch6\map\map_test.go:40: one 1
h:\go_learning\src\ch6\map\map_test.go:40: two 2
h:\go_learning\src\ch6\map\map_test.go:40: three 3
— PASS: TestMapTravel (0.00s)
PASS
ok cs.liangjiajia.com/ch6/map 0.040s

Map增删改查

map_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Map增删改查
func TestMapOperation(t *testing.T) {
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
m["four"] = 5 // add
t.Log(m, len(m))

m["four"] = 4 // update
t.Log(m, len(m))

t.Log(m["four"]) // select

delete(m, "four") //delete
t.Log(m, len(m))
}

=== RUN TestMapOperation
h:\go_learning\src\ch6\map\map_test.go:55: map[four:5 one:1 three:3 two:2] 4
h:\go_learning\src\ch6\map\map_test.go:58: map[four:4 one:1 three:3 two:2] 4
h:\go_learning\src\ch6\map\map_test.go:60: 4
h:\go_learning\src\ch6\map\map_test.go:63: map[one:1 three:3 two:2] 3
— PASS: TestMapOperation (0.00s)
PASS
ok cs.liangjiajia.com/ch6/map 1.517s

字符串

Go\text {Go} 语言中,字符串的长度(len函数返回的值)是按字节数来计算的,而不是按字符数。这意味着一个字符串的长度表示的是它包含的字节数,而不是字符数。所以,不同字符的 UTF-8\text {UTF-8} 编码占用的字节数不同,因此导致字符串的长度不同。

  • 一个英文字符等于一个字节
  • 一个中文(含繁体)等于三个字节
  • 一个中文标点占三个字节
  • 一个英文标点占一个字节

src\ch7\string

string_test.go

1
2
3
4
5
6
7
// 字符串对应字节数组长度
func TestStringLen(t *testing.T) {
s := "hello"
t.Log(s, len(s))
s = "中"
t.Log(s, len(s))
}

=== RUN TestString
h:\go_learning\src\ch7\string\string_test.go:7: hello 5
h:\go_learning\src\ch7\string\string_test.go:9: 中 3
— PASS: TestString (0.00s)
PASS
ok cs.liangjiajia.com/ch7/string 1.609s

可以将字符串转换为rune切片,每个rune元素表示一个字符的 Unicode\text {Unicode} 码点,这允许处理和操作 Unicode\text{Unicode} 字符。

string_test.go

1
2
3
4
5
6
7
8
9
10
// 字符串长度
func TestStringToRune(t *testing.T) {
s := "\xe4\xba\xba\xe6\xb0\x91\xe4\xb8\x87\xe5\xb2\x81"
t.Log(s, len(s))
c := []rune(s) // \u4EBA\u6C11\u4E07\u5C81 <-> 20154 27665 19975 23681
t.Log(c, len(c))
for _, c := range s {
t.Logf("%[1]c %[1]x", c)
}
}

=== RUN TestStringToRune
h:\go_learning\src\ch7\string\string_test.go:14: 人民万岁 12
h:\go_learning\src\ch7\string\string_test.go:16: [20154 27665 19975 23681] 4
h:\go_learning\src\ch7\string\string_test.go:18: 人 4eba
h:\go_learning\src\ch7\string\string_test.go:18: 民 6c11
h:\go_learning\src\ch7\string\string_test.go:18: 万 4e07
h:\go_learning\src\ch7\string\string_test.go:18: 岁 5c81
— PASS: TestStringToRune (0.00s)
PASS
ok cs.liangjiajia.com/ch7/string (cached)

string_test.go

1
2
3
4
5
6
7
8
9
// 字符串相关函数
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, "-"))
}

=== RUN TestStringFn
h:\go_learning\src\ch7\string\string_test.go:33: A
h:\go_learning\src\ch7\string\string_test.go:33: B
h:\go_learning\src\ch7\string\string_test.go:33: C
h:\go_learning\src\ch7\string\string_test.go:35: A-B-C
— PASS: TestStringFn (0.00s)
PASS
ok cs.liangjiajia.com/ch7/string 1.493s

string_test.go

1
2
3
4
5
6
7
8
9
// String与数字相互转换
func TestStringConvDigital(t *testing.T) {
s := strconv.Itoa(10) // digital:10 --> string:10
t.Log("10" + s)

if i, err := strconv.Atoi("10"); err == nil { // string:10 --> digital:10
t.Log(10 + i)
}
}

=== RUN TestStringConvDigital
h:\go_learning\src\ch7\string\string_test.go:41: 1010
h:\go_learning\src\ch7\string\string_test.go:44: 20
— PASS: TestStringConvDigital (0.00s)
PASS
ok cs.liangjiajia.com/ch7/string 0.040s

函数

src\ch8\function

function_test.go:计算一个函数所花的时间(使用函数作为参数和返回值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func timeSpent(inner func()) func() {
return func() {
start := time.Now()
inner()
fmt.Println("time spent:", time.Since(start).Seconds())
}
}

func slowFun() {
time.Sleep(time.Second * 1)
}

func TestFunction(t *testing.T) {
tsSF := timeSpent(slowFun)
tsSF()
}

=== RUN TestFunction
time spent: 1.0077673
— PASS: TestFunction (1.01s)
PASS
ok cs.liangjiajia.com/ch8/function 6.640s

function_test.go:计算任意多个值得和(函数可变参数)。

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 TestFunctionVarParam(t *testing.T) {
t.Log(Sum(1, 2, 3, 4))
t.Log(Sum(1, 2, 3, 4, 5))
}

=== RUN TestFunctionVarParam
h:\go_learning\src\ch8\function\function_test.go:35: 10
h:\go_learning\src\ch8\function\function_test.go:36: 15
— PASS: TestFunctionVarParam (0.00s)
PASS
ok cs.liangjiajia.com/ch8/function 5.636s

function_test.go:抛出异常,释放资源(defer\text {defer} 函数)。

1
2
3
4
5
6
7
func TestFunctionDefer(t *testing.T) {
defer func() {
t.Log("Clear resources")
}()
t.Log("Started")
panic("Fatal error")
}

=== RUN TestFunctionDefer
h:\go_learning\src\ch8\function\function_test.go:43: Started
h:\go_learning\src\ch8\function\function_test.go:41: Clear resources
— FAIL: TestFunctionDefer (0.00s)
panic: Fatal error [recovered]
panic: Fatal error

面向对象编程

类和对象

1
2
3
4
5
6
type struct_variable_type struct {
member definition
member definition
...
member definition
}

src\ch9\object

属性定义

encapsulation\encapsulation_test.go

1
2
3
4
5
type Employee struct {
Id string
Name string
Age int
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func TestCreateEmployeeObj(t *testing.T) {
e1 := Employee{"0", "Bob", 20}
t.Log(e1)

e2 := Employee{Name: "Mike", Age: 30}
t.Log(e2)

e3 := new(Employee)
e3.Id = "2"
e3.Age = 22
e3.Name = "Rose"
t.Log(e3)

t.Logf("e1 is %T", e1)
t.Logf("e2 is %T", e2)
t.Logf("e3 is %T", e3)
}

=== RUN TestCreateEmployeeObj
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:17: {0 Bob 20}
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:20: { Mike 30}
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:26: &{2 Rose 22}
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:28: e1 is encapsulation_test.Employee
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:29: e2 is encapsulation_test.Employee
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:30: e3 is *encapsulation_test.Employee
— PASS: TestCreateEmployeeObj (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object/encapsulation 0.038s

行为定义

encapsulation\encapsulation_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (e *Employee) String1() string {
return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}

func (e Employee) String2() string {
return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}

func TestStructOperations(t *testing.T) {
e1 := Employee{"0", "Bob", 20}
t.Log(e1.String1())
t.Log(e1.String2())

e2 := &Employee{"1", "Alice", 20}
t.Log(e2.String1())
t.Log(e2.String2())
}

=== RUN TestStructOperations
h:\go_learning\src\ch9\object\encap_test.go:42: ID:0/Name:Bob/Age:20
h:\go_learning\src\ch9\object\encap_test.go:43: ID:0-Name:Bob-Age:20
h:\go_learning\src\ch9\object\encap_test.go:46: ID:1/Name:Alice/Age:20
h:\go_learning\src\ch9\object\encap_test.go:47: ID:1-Name:Alice-Age:20
— PASS: TestStructOperations (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object 1.430s

通过指针和实例来表述是一样的。

encapsulation\encapsulation_test.go:通过地址打印查看区别

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
func (e *Employee) String1() 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) String2() 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 TestStructOperations(t *testing.T) {
e1 := Employee{"0", "Bob", 20}
fmt.Printf("Address is %x\n", unsafe.Pointer(&e1.Name))
fmt.Println()
t.Log(e1.String1())
t.Log(e1.String2())

fmt.Println()

e2 := &Employee{"1", "Alice", 20}
fmt.Printf("Address is %x\n", unsafe.Pointer(&e2.Name))
fmt.Println()
t.Log(e2.String1())
t.Log(e2.String2())
}

=== RUN TestStructOperations
Address is c000074910

Address is c000074910
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:47: ID:0/Name:Bob/Age:20
Address is c000074970
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:48: ID:0-Name:Bob-Age:20

Address is c0000749a0

Address is c0000749a0
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:55: ID:1/Name:Alice/Age:20
Address is c0000749d0
h:\go_learning\src\ch9\object\encapsulation\encapsulation_test.go:56: ID:1-Name:Alice-Age:20
— PASS: TestStructOperations (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object/encapsulation 1.514s

接口

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
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

type struct_name struct {
   /* variables */
}

func (struct_name_variable struct_name) method_name1() [return_type] {
   // interface method_name1 implementation
}

func (struct_name_variable struct_name) method_name2() [return_type] {
   // interface method_name2 implementation
}

...

func (struct_name_variable struct_name) method_namen() [return_type] {
   // interface method_namen implementation
}

src\ch9\object

interface\interface_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Programmer interface {
WriteHelloWorld() string
}

type GoProgrammer struct {
}

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

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

=== RUN TestClient
h:\go_learning\src\ch9\object\interface\interface_test.go:18: fmt.Println(“Hello World”)
— PASS: TestClient (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object/interface 0.039s

空接口

interface\empty_interface_test.go

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 DoSomething(p interface{}) {
// if i, ok := p.(int); ok {
// fmt.Println("Integer", i)
// return
// }
// if s, ok := p.(string); ok {
// fmt.Println("stirng", s)
// return
// }
// fmt.Println("Unknow Type")
switch v := p.(type) {
case int:
fmt.Println("Integer", v)
case string:
fmt.Println("String", v)
default:
fmt.Println("Unknow Type")
}
}

func TestEmptyInterfaceAssertion(t *testing.T) {
DoSomething(10)
DoSomething("10")
}

=== RUN TestEmptyInterfaceAssertion
Integer 10
String 10
— PASS: TestEmptyInterfaceAssertion (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object/interface 1.447s

自定义类型

src\ch9\object

customer_type_test.go

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 object_test

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

type IntConv func()

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

func slowFun() {
time.Sleep(time.Second * 1)
}

func TestFunction(t *testing.T) {
tsSF := timeSpent(slowFun)
tsSF()
}

=== RUN TestFunction
time spent: 1.0059892
— PASS: TestFunction (1.01s)
PASS
ok cs.liangjiajia.com/ch9/object (cached)

拓展复用

src\ch9\object

extension\extension_test.go:宠物类

1
2
3
4
5
6
7
8
9
10
11
type Pet struct {
}

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

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

extension\extension_test.go:狗类

1
2
3
4
5
6
7
8
9
10
11
type Dog struct {
p *Pet
}

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

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

上述也可以简单表示:extension\extension_test.go

1
2
3
type Dog struct {
Pet
}
1
2
3
4
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("solisamicus")
}

=== RUN TestDog
… solisamicus
— PASS: TestDog (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object 5.659s

做部分修改:extension\extension_test.go

1
2
3
4
5
6
7
8
9
10
11
12
type Dog struct {
p *Pet
}

func (d *Dog) Speak() {
fmt.Print("Woof Woof Woof")
}

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

=== RUN TestDog
Woof Woof Woof solisamicus
— PASS: TestDog (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object/extension 5.801s

多态

src\ch9\object

polymorphism\polymorphism_test.go

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
type Programmer interface {
WriteHelloWorld() string
}

type GoProgrammer struct {
}

type JavaProgrammer struct {
}

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

func (javaProgrammer *JavaProgrammer) WriteHelloWorld() string {
return "System.out.Println(\"Hello World!\")"
}

func writeFirstProgram(prog Programmer) {
fmt.Printf("%T %v\n", prog, prog.WriteHelloWorld())
}

func TestClient(t *testing.T) {
goProg := new(GoProgrammer)
javaProg := &JavaProgrammer{}
writeFirstProgram(goProg)
writeFirstProgram(javaProg)
}

=== RUN TestClient
*polymorphism_test.GoProgrammer fmt.Println(“Hello World”)
*polymorphism_test.JavaProgrammer System.out.Println(“Hello World!”)
— PASS: TestClient (0.00s)
PASS
ok cs.liangjiajia.com/ch9/object/polymorphism 1.522s

错误处理

src\ch10\error

error_test.go:对于 Fibonacci\text {Fibonacci} 数列要求 n[2,100]n \in [2,100]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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(1); err != nil {
t.Error(err)
} else {
t.Log(v)
}
}

=== RUN TestGetFibonacci
h:\go_learning\src\ch10\error\error_test.go:29: n should be not less than 2
— FAIL: TestGetFibonacci (0.00s)
FAIL
FAIL cs.liangjiajia.com/ch10/error 2.071s

其他两种改写方式:

1⃣error_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func GetFibonacci1(str string) {
var (
i int
err error
list []int
)
if i, err = strconv.Atoi(str); err == nil {
if list, err = GetFibonacci(i); err == nil {
fmt.Println(list)
} else {
fmt.Println("Error", err)
}
} else {
fmt.Println("Error", err)
}
}

func TestGetFibonacc1(t *testing.T) {
GetFibonacci1("1")
GetFibonacci1("10")
}

=== RUN TestGetFibonacc1
Error n should be not less than 2
[1 1 2 3 5 8 13 21 34 55]
— PASS: TestGetFibonacc1 (0.00s)
PASS
ok cs.liangjiajia.com/ch10/error 0.045s

2⃣error_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func GetFibonacci2(str string) {
var (
i int
err error
list []int
)
if i, err = strconv.Atoi(str); err != nil {
fmt.Println("Error", err)
return
}
if list, err = GetFibonacci(i); err != nil {
fmt.Println("Error", err)
return
}
fmt.Println(list)
}

func TestGetFibonacc2(t *testing.T) {
GetFibonacci2("1")
GetFibonacci2("10")
}

=== RUN TestGetFibonacc2
Error n should be not less than 2
[1 1 2 3 5 8 13 21 34 55]
— PASS: TestGetFibonacc2 (0.00s)
PASS
ok cs.liangjiajia.com/ch10/error 1.426s

包和依赖管理

Go\text {Go} 语言中的标识符(变量、函数、类型等)的可见性是由首字母的大小写来决定的。

  • 大写字母开头的标识符是公开的,可以被其他包访问;
  • 小写字母开头的标识符是私有的,只能在当前包内使用。

Go\text {Go} 语言的编译和导入机制并不直接要求包名和目录名一致,但强烈建议保持它们一致,以提高代码的可读性和维护性。

同一目录的 Go\text {Go}package\text {package} 要保持一致。

当建立工作空间目录后,需要把工作空间目录的路径添加的 GOPATH\text {GOPATH} 环境变量中。

src\ch11\series

my_series.go

1
2
3
4
5
6
7
8
9
package series

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

src\ch11\client

package_test.go

1
2
3
4
5
6
7
8
9
10
11
package client

import (
"testing"

"cs.liangjiajia.com/ch11/series"
)

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

=== RUN TestPackage
h:\go_learning\src\ch11\client\package_test.go:10: [1 1 2 3 5]
— PASS: TestPackage (0.00s)
PASS
ok cs.liangjiajia.com/ch11/client 1.539s

使用第三方库

go get -u github.com/easierway/concurrent_map

 
src\ch11\client

remote_package_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package client

import (
"testing"

cm "github.com/easierway/concurrent_map"
)

func TestConcurrentMap(t *testing.T) {
m := cm.CreateConcurrentMap(99)
m.Set(cm.StrKey("key"), 10)
t.Log(m.Get(cm.StrKey("key")))
}

=== RUN TestConcurrentMap
h:\go_learning\src\ch11\client\remote_package_test.go:12: 10 true
— PASS: TestConcurrentMap (0.00s)
PASS
ok cs.liangjiajia.com/ch11/client 1.148s

并发编程

协程机制

src\ch12\groutine

groutine_test.go

1
2
3
4
5
6
7
8
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
0
3
2
5
4
6
7
8
9
1
— PASS: TestGroutine (0.06s)
PASS
ok cs.liangjiajia.com/ch12/groutine (cached)

共享内存机制

src\ch12\share_memery
share_memery.go

1
2
3
4
5
6
7
8
9
10
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
h:\go_learning\src\ch12\share_memory\share_memery_test.go:17: counter = 4942
— PASS: TestCounter (1.02s)
PASS
ok cs.liangjiajia.com/ch12/share_memory 6.647s

在这个例子中,每个 goroutine\text {goroutine} 都试图递增一个共享的counter变量。由于 goroutines\text {goroutines} 并发执行,它们可能会同时访问和修改counter变量,导致竞争条件(Race Condition\text {Race Condition})。不同的 goroutine\text {goroutine} 可能在不同的时间点并发地执行counter++操作。最终的counter值就不再是预期的 50005000

share_memery.go:解决方法,使用互斥锁(Mutex\text {Mutex}来保护counter变量,确保同一时间只有一个 goroutine\text {goroutine} 可以访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
h:\go_learning\src\ch12\share_memory\share_memery_test.go:33: counter = 5000
— PASS: TestCounterThreadSafe (1.01s)
PASS
ok cs.liangjiajia.com/ch12/share_memory 1.054s

share_memery.gotime.Sleep(1 * time.Second)被用来等待足够的时间,以确保所有启动的 goroutines\text {goroutines} 都有机会执行递增操作,从而在最终打印计数器值之前完成。在这个例子中,goroutines\text {goroutines} 是并发执行的,执行时间是不确定的,因此在主 goroutine\text {goroutine}(测试函数)继续执行之前,使用Sleep来等待一秒钟,以确保足够的时间让其他 goroutines\text {goroutines} 完成它们的递增操作。需要注意的是,使用Sleep并不是一种理想的等待方式,因为它不能保证所有的 goroutines\text {goroutines} 都已经完成。更好的做法是使用等待组(WaitGroup\text {WaitGroup}等并发控制机制,这样可以更精确地等待所有 goroutines\text {goroutines} 完成后再继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
}

=== RUN TestCounterWaitGroup
h:\go_learning\src\ch12\share_memory\share_memery_test.go:52: counter = 5000
— PASS: TestCounterWaitGroup (0.00s)
PASS
ok cs.liangjiajia.com/ch12/share_memory 5.661s

CSP并发机制

Go\text {Go} 语言通过 CSP\text {CSP}Communicating Sequential Processes\text {Communicating Sequential Processes} 并发模型来实现并发编程。CSP\text {CSP} 是一种并发编程的范式,强调通过在不同执行体 goroutine\text {goroutine} 之间传递消息进行通信,而不是通过共享内存来进行数据共享。

核心组件:

goroutine\text {goroutine}(协程)Go\text {Go} 语言中轻量级的并发执行单元,可以看作是一个独立的执行线程。使用go关键字可以启动一个新的 goroutine\text {goroutine},它和主 goroutine\text {goroutine} 并发执行。由 Go\text {Go} 运行时系统调度,而不是由操作系统调度,创建和切换成本相对较低。

channel\text {channel}(通道):用于在不同goroutines之间传递数据的管道。通道可以是带缓冲的(缓冲通道)或非缓冲的。带缓冲通道允许在发送和接收之间有一定的缓冲空间,而非缓冲通道要求发送和接收同时准备好。通过使用通道,goroutines\text {goroutines} 可以安全地进行数据传递,避免了显式的锁和共享内存的复杂性。

src\ch12\async_service

async_service_test.go:串行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.17s)
PASS
ok cs.liangjiajia.com/ch12/async_service 5.886s

async_service_test.go:并行控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func AsyncService() chan string {
retCh := make(chan string)
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)
}

=== RUN TestAsynService
working on something else
returned result.
Task is done.
Done
service exited.
— PASS: TestAsynService (0.10s)
PASS
ok cs.liangjiajia.com/ch12/async_service 5.777s

执行过程:

  1. TestAsyncService 开始执行,启动主 goroutine\text {goroutine}

  2. AsyncService 被调用,启动一个 goroutine\text {goroutine}。在这个 goroutine\text {goroutine} 中,service被调用 ,休眠 5050 ms;

  3. otherTask被调用,启动一个 goroutine\text {goroutine}。在这个 goroutine\text {goroutine} 中,打印"working on something else\text {working on something else}",休眠 100100 ms;

  4. 5050 毫秒后,service完成,打印"returned result.\text {returned result.}",将 “Done\text {Done}” 通过通道传递给主 goroutine\text {goroutine}

另外的 100100 毫秒后,otherTask完成,打印"Task is done.\text {Task is done.}"。

  1. goroutine\text {goroutine} 从通道中接收到"Done\text {Done}",打印"Done\text {Done}";

  2. AsyncService中的 goroutine\text {goroutine} 继续执行,打印 “service exited.\text {service exited.}”。

servicegoroutine\text {goroutine} 会发生堵塞,直到接收 channel\text {channel}

async_service_test.go:稍作修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
}

=== RUN TestAsynService
working on something else
returned result.
service exited.
Task is done.
Done
— PASS: TestAsynService (0.11s)
PASS
ok cs.liangjiajia.com/ch12/async_service (cached)

多路选择和超时控制

src\ch12\select_case

select_case_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 TestSelect(t *testing.T) {
select {
case ret := <-AsyncService():
t.Log(ret)
case <-time.After(time.Millisecond * 100):
t.Error("time out")
}
}

select_case_test.go:设置service等待时间为 5050 ms

1
2
3
4
func service() string {
time.Sleep(time.Millisecond * 50)
return "Done"
}

=== RUN TestSelect
returned result.
service exited.
h:\go_learning\src\ch12\select_case\select_case_test.go:28: Done
— PASS: TestSelect (0.06s)
PASS
ok cs.liangjiajia.com/ch12/select_case 5.687s

select_case_test.go:设置service等待时间为 500500 ms

1
2
3
4
func service() string {
time.Sleep(time.Millisecond * 500)
return "Done"
}

=== RUN TestSelect
h:\go_learning\src\ch12\select_case\select_case_test.go:30: time out
— FAIL: TestSelect (0.10s)
FAIL
FAIL cs.liangjiajia.com/ch12/select_case 5.778s

Channel的关闭和消息广播

src\ch12\channel

channel_close_test.go:单个接收者

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

func dataReceiver(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)
dataReceiver(ch, &wg)
wg.Wait()
}

=== RUN TestCloseChannel
0
1
2
3
4
5
6
7
8
9
— PASS: TestCloseChannel (0.00s)
PASS
ok cs.liangjiajia.com/ch12/channel 1.640s

channel_close_test.go:多个接收者

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 dataReceiverCorrection(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)
dataReceiverCorrection(ch, &wg)
wg.Add(1)
dataReceiverCorrection(ch, &wg)
wg.Add(1)
dataReceiverCorrection(ch, &wg)
wg.Wait()
}

=== RUN TestCloseChannel
0
2
3
4
1
6
7
8
9
5
— PASS: TestCloseChannel (0.00s)
PASS
ok cs.liangjiajia.com/ch12/channel 1.440s

普通任务取消

src\ch12\cancel

cancel_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cancel

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

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

1⃣cancel_test.go:发送空消息

1
2
3
4
5
// cancel_1 sends a cancellation signal to the provided channel.
// It does so by sending an empty struct to the channel.
func cancel_1(cancelChan chan struct{}) {
cancelChan <- struct{}{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
PASS
ok cs.liangjiajia.com/ch12/cancel 6.779s

2⃣cancel_test.go:关闭通道

1
2
3
4
5
// cancel_2 sends a cancellation signal to the provided channel.
// It does so by closing the channel.
func cancel_2(cancelChan chan struct{}) {
close(cancelChan)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.01s)
PASS
ok cs.liangjiajia.com/ch12/cancel 6.547s

关联任务取消

src\ch12\cancel

cancel_by_context_test.go

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
func isCancelledContext(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}

func TestCancelByContext(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
for i := 0; i < 5; i++ {
go func(i int, ctx context.Context) {
for {
if isCancelledContext(ctx) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "Cancelled")
}(i, ctx)
}
cancel()
time.Sleep(time.Second * 1)
}

典型并发任务

src\ch12\concurrent_tasks

单例模式

singleton\singleton_test.go

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
type Singleton struct {
data string
}

var once sync.Once

var singleInstance *Singleton

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("%X\n", unsafe.Pointer(obj))
wg.Done()
}()
}
wg.Wait()
}

=== RUN TestGetSingletonObj
Create Obj
C00008A000
C00008A000
C00008A000
C00008A000
C00008A000
C00008A000
C00008A000
C00008A000
C00008A000
C00008A000
— PASS: TestGetSingletonObj (0.00s)
PASS
ok cs.liangjiajia.com/ch12/concurrent_tasks/singleton 1.778s

返回第一个

first_response\first_response_test.go:多个任务的并发执行,但只等待第一个返回的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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(FirstResponse())
}

=== RUN TestFirstResponse
h:\go_learning\src\ch12\concurrent_tasks\first_response\first_response_test.go:27: The result is from 9
— PASS: TestFirstResponse (0.02s)
PASS
ok cs.liangjiajia.com/ch12/concurrent_tasks/first_response 5.685s

完成所有任务

all_response\all_response_test.go:多个任务的并发执行,等待所有返回的结果。

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 runTask(id int) string {
time.Sleep(10 * time.Millisecond)
return fmt.Sprintf("The result is from %d", id)
}

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 += "\n" + <-ch

}
return finalRet
}

func TestFirstResponse(t *testing.T) {
t.Log(AllResponse())
time.Sleep(time.Second * 1)
}

=== RUN TestFirstResponse
h:\go_learning\src\ch12\concurrent_tasks\all_response\all_response_test.go:32:
The result is from 1
The result is from 6
The result is from 9
The result is from 7
The result is from 0
The result is from 4
The result is from 5
The result is from 3
The result is from 2
The result is from 8
— PASS: TestFirstResponse (1.02s)
PASS
ok cs.liangjiajia.com/ch12/concurrent_tasks/all_response 6.766s

对象池

src\ch12\object_pool

obj_pool.go

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
type ReusableObj struct {
// Structure for reusable objects
}

type ObjPool struct {
bufChan chan *ReusableObj // Channel for buffering reusable objects
}

// NewObjPool creates an object pool, initializes, and fills it with a specified number of reusable objects
func NewObjPool(numOfObj int) *ObjPool {
objPool := ObjPool{}
objPool.bufChan = make(chan *ReusableObj, numOfObj)
for i := 0; i < numOfObj; i++ {
objPool.bufChan <- &ReusableObj{} // Put a pointer to a reusable object into the channel
}
return &objPool
}

// GetObj retrieves a reusable object from the object pool with a specified timeout; returns an error if timed out
func (objPool *ObjPool) GetObj(timeout time.Duration) (*ReusableObj, error) {
select {
case ret := <-objPool.bufChan:
return ret, nil // Return if there is a reusable object in the channel
case <-time.After(timeout): // Return a timeout error if timed out
return nil, errors.New("time out")
}
}

// ReleaseObj puts an object back into the object pool; returns an error if the pool is full
func (objPool *ObjPool) ReleaseObj(obj *ReusableObj) error {
select {
case objPool.bufChan <- obj:
return nil // Return nil if successful (object released)
default:
return errors.New("overflow") // Return an overflow error if the channel is full
}
}

obj_pool_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TestObjPool(t *testing.T) {
pool := NewObjPool(10)
useCount := 15
for i := 0; i < useCount; i++ {
if v, err := pool.GetObj(time.Second * 1); err != nil {
t.Error(err)
} else {
fmt.Println(&v)
if err := pool.ReleaseObj(v); err != nil {
t.Error(err)
}
}

}
fmt.Println("Done")
}

=== RUN TestObjPool
0xc00000a068
0xc00000a070
0xc00000a078
0xc00000a080
0xc00000a088
0xc00000a090
0xc00000a098
0xc00000a0a0
0xc00000a0a8
0xc00000a0b0
0xc00000a0b8
0xc00000a0c0
0xc00000a0c8
0xc00000a0d0
0xc00000a0d8
Done
— PASS: TestObjPool (0.00s)
PASS
ok cs.liangjiajia.com/ch12/object_pool 1.435s

sysc.Pool 对象缓存

对象获取:

  1. 尝试从私有对象获取
  2. 尝试从当前 Processor\text {Processor} 共享池获取
  3. 尝试从其他 Processor\text {Processor} 共享池获取
  4. 用户指定 New\text {New} 函数产生新的对象

对象放回:

  1. 保存为私有对象
  2. 保存为当前 Processor\text {Processor} 共享池

src\ch12\object_cache

sysnc_pool_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
func TestSyncPool(t *testing.T) {
pool := &sync.Pool{
New: func() interface{} {
fmt.Println("Create a new object.")
return 100
},
}
v1 := pool.Get().(int)
fmt.Println(v1)
pool.Put(2023)
v2, _ := pool.Get().(int)
fmt.Println(v2)
}

=== RUN TestSyncPool
Create a new object.
100
2023
— PASS: TestSyncPool (0.00s)
PASS
ok cs.liangjiajia.com/ch12/object_cache (cached)

sysnc_pool_test.go:添加runtime.GC():官方 Go runtime 1.13\text {Go runtime 1.13} 将对sync.Pool中的对象回收时机策略做出调整。在 1.121.12 版本及以前的版本中,在每轮垃圾回收过程中,每个 sync.Pool\text {sync.Pool} 实例中的所有缓存对象都将被无条件回收掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func TestSyncPool(t *testing.T) {
pool := &sync.Pool{
New: func() interface{} {
fmt.Println("Create a new object.")
return 100
},
}
v1 := pool.Get().(int)
fmt.Println(v1)
pool.Put(2023)
runtime.GC()
v2, _ := pool.Get().(int)
fmt.Println(v2)
}

=== RUN TestSyncPool
Create a new object.
100
2023
— PASS: TestSyncPool (0.00s)
PASS
ok cs.liangjiajia.com/ch12/object_cache (cached)

sysnc_pool_test.go:从 1.13\text {1.13} 版本开始,如果一个 sync.Pool\text {sync.Pool} 实例在上一轮垃圾回收过程结束之后仍然被使用过,则其中的缓存对象将不会被回收掉。此举对于使用 sync.Pool\text {sync.Pool} 来提升效率的程序来说,将大大减少周期性的因为缓存被清除而造成的瞬时效率下降。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func TestSyncPool(t *testing.T) {
pool := &sync.Pool{
New: func() interface{} {
fmt.Println("Create a new object.")
return 100
},
}
v1 := pool.Get().(int)
fmt.Println(v1)
pool.Put(2023)
runtime.GC()
runtime.GC()
v2, _ := pool.Get().(int)
fmt.Println(v2)
}

=== RUN TestSyncPool
Create a new object.
100
Create a new object.
100
— PASS: TestSyncPool (0.00s)
PASS
ok cs.liangjiajia.com/ch12/object_cache (cached)

sysnc_pool_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
ok cs.liangjiajia.com/ch12/object_cache 0.037s

测试

单元测试

src\ch13\unit_test

square.go

1
2
3
func square(op int) int {
return op * op
}

square_test.go

1
2
3
4
5
6
7
8
9
10
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)
}
}
}

=== RUN TestSquare
— PASS: TestSquare (0.00s)
PASS
ok cs.liangjiajia.com/ch13/unit_test 1.270s

FailError:测试失败,测试继续,其他测试继续执行。

FailNowFatal:测试失败,测试终止,其他测试继续执行。

square_test.go

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

=== RUN TestSquare
— PASS: TestSquare (0.00s)
=== RUN TestErrorInCode
Start
h:\go_learning\src\ch13\unit_test\square_test.go:21: Error
End
— FAIL: TestErrorInCode (0.00s)
=== RUN TestFailInCode
Start
h:\go_learning\src\ch13\unit_test\square_test.go:27: Error
— FAIL: TestFailInCode (0.00s)
FAIL
FAIL cs.liangjiajia.com/ch13/unit_test 1.415s

断言

go get -u github.com/stretchr/testify/assert

`square_test.go`:
1
2
3
4
5
6
7
8
func TestSquareWithAssert(t *testing.T) {
inputs := [...]int{1, 2, 3}
expected := [...]int{1, 4, 9}
for i := 0; i < len(inputs); i++ {
ret := square(inputs[i])
assert.Equal(t, expected[i], ret)
}
}

=== RUN TestSquareWithAssert
— PASS: TestSquareWithAssert (0.00s)
PASS
ok cs.liangjiajia.com/ch13/unit_test 0.940s

发生错误的情况

=== RUN TestSquareWithAssert
h:\go_learning\src\ch13\unit_test\square_test.go:38:
Error Trace: h:/go_learning/src/ch13/unit_test/square_test.go:38
Error: Not equal:
expected: 10
actual : 9
Test: TestSquareWithAssert
— FAIL: TestSquareWithAssert (0.00s)
FAIL
FAIL cs.liangjiajia.com/ch13/unit_test 1.819s

Benchmark

src\ch13\benchmark

concat_test.go

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
42
43
func TestConcatStringByAdd(t *testing.T) {
assert := assert.New(t)
elems := []string{"1", "2", "3", "4", "5"}
ret := ""
for _, elem := range elems {
ret += elem
}
assert.Equal("12345", ret)
}

func TestConcatStringByBytesBuffer(t *testing.T) {
assert := assert.New()
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 BenchmarkConcatStringByBytesBuffer(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: windows
goarch: amd64
pkg: cs.liangjiajia.com/ch13/benchmark
cpu: Intel® Core™ i7-9750H CPU @ 2.60GHz
BenchmarkConcatStringByAdd-12 11036227 105.3 ns/op
BenchmarkConcatStringByBytesBuffer-12 21769615 54.71 ns/op
PASS
ok cs.liangjiajia.com/ch13/benchmark 4.115s

go test -bench="." -benchmem

goos: windows
goarch: amd64
pkg: cs.liangjiajia.com/ch13/benchmark
cpu: Intel® Core™ i7-9750H CPU @ 2.60GHz
BenchmarkConcatStringByAdd-12 11269498 108.7 ns/op 16 B/op 4 allocs/op
BenchmarkConcatStringByBytesBuffer-12 19745870 56.24 ns/op 64 B/op 1 allocs/op
PASS
ok cs.liangjiajia.com/ch13/benchmark 2.554s

BDD-Behavior Driven Development

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

src\ch13\bdd

bdd_test.go

1
2
3
4
5
6
7
8
9
10
11
12
func TestSpec(t *testing.T) {
convey.Convey("Given 2 even numbers", t, func() {
a := 3
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返回类型reflect.Type

reflect.ValueOf返回值reflect.Value

判断类型:

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
type Kind uint

const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)

src\ch14\reflect

reflect_test.go

1
2
3
4
5
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
h:\go_learning\src\ch14\reflect\reflect_test.go:11: int64 10
h:\go_learning\src\ch14\reflect\reflect_test.go:12: int64
— PASS: TestTypeAndValue (0.00s)
PASS
ok cs.liangjiajia.com/ch14/reflect 0.040s

reflect_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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("Unknown", t)
}
}

func TestBasicType(t *testing.T) {
var f float64 = 12
CheckType(f)
}

=== RUN TestBasicType
Float
— PASS: TestBasicType (0.00s)
PASS
ok cs.liangjiajia.com/ch14/reflect 1.529s

利用反射编写灵活的代码:

  • 按名字访问结构的成员:reflect.ValueOf(*e).FieldByName("XXX")
  • 按名字访问结构的方法:reflect.ValueOf(e).MethodByName("XXX").Call([]reflect.Value{reflect.ValueOf(x)})

reflect_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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(18)})
t.Log("Updated Age:", e)
}

=== RUN TestInvokeByName
h:\go_learning\src\ch14\reflect\reflect_test.go:44: Name: value(Mike), Type(reflect.Value)
h:\go_learning\src\ch14\reflect\reflect_test.go:48: Tag:format normal
h:\go_learning\src\ch14\reflect\reflect_test.go:51: Updated Age: &{1 Mike 18}
— PASS: TestInvokeByName (0.00s)
PASS
ok cs.liangjiajia.com/ch14/reflect (cached)

万能程序

reflect_deepequal_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Customer struct {
CookieID string
Name string
Age int
}

func TestDeepEqual(t *testing.T) {
a := map[int]string{1: "one", 2: "two", 3: "three"}
b := map[int]string{1: "one", 2: "two", 3: "three"}
t.Log("a==b?", 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))

c1 := Customer{"1", "Mike", 40}
c2 := Customer{"1", "Mike", 40}
t.Log("c1 == c2?", reflect.DeepEqual(c1, c2))
}

=== RUN TestDeepEqual
h:\go_learning\src\ch14\reflect\reflect_deepequal_test.go:17: a==b? true
h:\go_learning\src\ch14\reflect\reflect_deepequal_test.go:22: s1 == s2? true
h:\go_learning\src\ch14\reflect\reflect_deepequal_test.go:23: s1 == s3? false
h:\go_learning\src\ch14\reflect\reflect_deepequal_test.go:27: c1 == c2? true
— PASS: TestDeepEqual (0.00s)
PASS
ok cs.liangjiajia.com/ch14/reflect 1.636s

reflect_deepequal_test.go:我们可以观察到Employee类和Customer类结构一致,所以可以在值一样的情况下完成填充

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
func fillBySettings(st interface{}, settings map[string]interface{}) error {
// 检查 st 是一个指针
if reflect.TypeOf(st).Kind() != reflect.Ptr {
return errors.New("The first param should be a pointer to the struct type.")
}
// 检查 st 指针所指向的类型是结构体
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
func TestFillNameAndAge(t *testing.T) {
settings := map[string]interface{}{"Name": "Mike", "Age": 30}
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)
}

=== RUN TestFillNameAndAge
h:\go_learning\src\ch14\reflect\reflect_deepequal_test.go:66: { Mike 30}
h:\go_learning\src\ch14\reflect\reflect_deepequal_test.go:71: { Mike 30}
— PASS: TestFillNameAndAge (0.00s)
PASS
ok cs.liangjiajia.com/ch14/reflect 1.825s

常见架构模式实现

pipe-filter

src\ch15\framework

filter.go定义:

1
2
3
4
5
6
7
8
9
10
11
// Request is the input of the filter
type Request interface{}

// Response is the output of the filter
type Response interface{}

// Filter interface is the definition of the data processing components
// Pipe-Filter structure
type Filter interface {
Process(data Request) (Response, error)
}

split_filter.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var SplitFilterWrongFormatError = errors.New("Input data should be string")

type SplitFilter struct {
delimiter string
}

func NewSplitFilter(delimiter string) *SplitFilter {
return &SplitFilter{delimiter}
}

func (sf *SplitFilter) Process(data Request) (Response, error) {
str, ok := data.(string)
if !ok {
return nil, SplitFilterWrongFormatError
}
parts := strings.Split(str, sf.delimiter)
return parts, nil
}

to_int_filter.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var ToIntFilterWrongFormatError = errors.New("Input data should be []string")

type ToIntFilter struct {
}

func NewToIntFilter() *ToIntFilter {
return &ToIntFilter{}
}

func (tif *ToIntFilter) Process(data Request) (Response, error) {
parts, ok := data.([]string)
if !ok {
return nil, ToIntFilterWrongFormatError
}
ret := []int{}
for _, part := range parts {
s, err := strconv.Atoi(part)
if err != nil {
return nil, err
}
ret = append(ret, s)
}
return ret, nil
}

sum_filter.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var SumFilterWrongFormatError = errors.New("Input data should be []int")

type SumFilter struct {
}

func NewSumFilter() *SumFilter {
return &SumFilter{}
}

func (sf *SumFilter) Process(data Request) (Response, error) {
elems, ok := data.([]int)
if !ok {
return nil, SumFilterWrongFormatError
}
ret := 0
for _, elem := range elems {
ret += elem
}
return ret, nil
}

straigt_pipeline.go:整合形成

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
// NewStraightPipeline create a new StraightPipelineWithWallTime
func NewStraightPipeline(name string, filters ...Filter) *StraightPipeline {
return &StraightPipeline{
Name: name,
Filters: &filters,
}
}

// StraightPipeline is composed of the filters, and the filters are piled as a straigt line.
type StraightPipeline struct {
Name string
Filters *[]Filter
}

// Process is to process the coming data by the pipeline
func (f *StraightPipeline) Process(data Request) (Response, error) {
var ret interface{}
var err error
for _, filter := range *f.Filters {
ret, err = filter.Process(data)
if err != nil {
return ret, err
}
data = ret
}
return ret, err
}

pipefilter_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
func TestStraightPipeline(t *testing.T) {
split_filter := NewSplitFilter(",")
to_int_filter := NewToIntFilter()
sum_filter := NewSumFilter()
sp := NewStraightPipeline("calculate", split_filter, to_int_filter, sum_filter)
ret, err := sp.Process("1,2,3")
if err != nil {
t.Fatal(err)
}
if ret != 6 {
t.Fatalf("The expected is 6, but the actual is %d", ret)
}
}

=== RUN TestStraightPipeline
— PASS: TestStraightPipeline (0.00s)
PASS
ok cs.liangjiajia.com/ch15/framework/pipe_filter 1.404s

micro-kernel

常见任务

JSON

src\ch16\task\json

struct_def.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type BasicInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
type JobInfo struct {
Skills []string `json:"skills"`
}
type Employee struct {
BasicInfo BasicInfo `json:"basic_info"`
JobInfo JobInfo `json:"job_info"`
}

var jsonStr = `{
"basic_info":{
"name":"Mike",
"age":30
},
"job_info":{
"skills":["Java","Go","C"]
}
} `

embedded_json_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
func TestEmbeddedJson(t *testing.T) {
e := new(Employee)
err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
t.Error(err)
}
fmt.Println(*e)
if v, err := json.Marshal(e); err == nil {
fmt.Println(string(v))
} else {
t.Error(err)
}
}

=== RUN TestEmbeddedJson
{ {Mike 30} {[Java Go C]} }
{“basic_info”:{“name”:“Mike”,“age”:30},“job_info”:{“skills”:[“Java”,“Go”,“C”]}}
— PASS: TestEmbeddedJson (0.00s)
PASS
ok cs.liangjiajia.com/ch16/task/json 1.437s

easyjson

cd\text {cd}src\ch16,执行

go get -u github.com/mailru/easyjson & go install github.com/mailru/easyjson/...@latest

cd\text {cd}src\ch16\task\json,执行

~\go\bin\easyjson -all struct_def.go

自动生成struct_def_easyjson.go文件

easy_json_test.go

1
2
3
4
5
6
7
8
9
10
11
func TestEasyJson(t *testing.T) {
e := Employee{}
e.UnmarshalJSON([]byte(jsonStr))
fmt.Println(e)
if v, err := e.MarshalJSON(); err != nil {
t.Error(err)
} else {
fmt.Println(string(v))
}
}

=== RUN TestEasyJson
{ {Mike 30} {[Java Go C]} }
{“basic_info”:{“name”:“Mike”,“age”:30},“job_info”:{“skills”:[“Java”,“Go”,“C”]}}
— PASS: TestEasyJson (0.00s)
PASS
ok cs.liangjiajia.com/ch16/task/json 1.662s

benchmark_json_test.go

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
func BenchmarkEmbeddedJson(b *testing.B) {
b.ResetTimer()
e := new(Employee)
for i := 0; i < b.N; i++ {

err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
b.Error(err)
}
if _, err = json.Marshal(e); err != nil {
b.Error(err)
}
}
}

func BenchmarkEasyJson(b *testing.B) {
b.ResetTimer()
e := Employee{}
for i := 0; i < b.N; i++ {
err := e.UnmarshalJSON([]byte(jsonStr))
if err != nil {
b.Error(err)
}
if _, err = e.MarshalJSON(); err != nil {
b.Error(err)
}
}
}

cd\text {cd}src\ch16\task\json,执行

go test -bench="."

{ {Mike 30} {[Java Go C]} }
{“basic_info”:{“name”:“Mike”,“age”:30},“job_info”:{“skills”:[“Java”,“Go”,“C”]}}
{ {Mike 30} {[Java Go C]} }
{“basic_info”:{“name”:“Mike”,“age”:30},“job_info”:{“skills”:[“Java”,“Go”,“C”]}}
goos: windows
goarch: amd64
pkg: cs.liangjiajia.com/ch16/task/json
cpu: Intel® Core™ i7-9750H CPU @ 2.60GHz
BenchmarkEmbeddedJson-12 530538 2283 ns/op
BenchmarkEasyJson-12 1568922 717.6 ns/op
PASS
ok cs.liangjiajia.com/ch16/task/json 3.182s

HTTP

src\ch16\task\http

hello_http.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"net/http"
"time"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.HandleFunc("/time/", func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
timeStr := fmt.Sprintf("{\"time\": \"%s\"}", t)
w.Write([]byte(timeStr))
})

http.ListenAndServe(":8080", nil)
}

http://127.0.0.1:8080/

http://127.0.0.1:8080/time/

restful

cd\text {cd}src\ch16,执行

go get -u github.com/julienschmidt/httprouter

http_router\http_router_http.go

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 main

import (
"fmt"
"log"
"net/http"

"github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)

log.Fatal(http.ListenAndServe(":8080", router))
}

http://127.0.0.1:8080/

http://127.0.0.1:8080/hello/:solisamicus

restful\restful_http.go

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
42
43
44
type Employee struct {
ID string `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}

var employeeDB map[string]*Employee

func init() {
employeeDB = map[string]*Employee{}
employeeDB["Mike"] = &Employee{"e-1", "Mike", 35}
employeeDB["Rose"] = &Employee{"e-2", "Rose", 45}
}

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}

func GetEmployeeByName(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
qName := ps.ByName("name")
var (
ok bool
info *Employee
infoJson []byte
err error
)
if info, ok = employeeDB[qName]; !ok {
w.Write([]byte("{\"error\":\"Not Found\"}"))
return
}
if infoJson, err = json.Marshal(info); err != nil {
w.Write([]byte(fmt.Sprintf("{\"error\":,\"%s\"}", err)))
return
}
w.Write(infoJson)
}

func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/employee/:name", GetEmployeeByName)

log.Fatal(http.ListenAndServe(":8080", router))
}

http://127.0.0.1:8080/

http://127.0.0.1:8080/employee/Mike

性能调优

graphviz

官网:https://www.graphviz.org/

下载:https://graphviz.org/download/

flamegraph

下载并复制 flamegraph.pl\text {flamegraph.pl}https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl)至 \text {\$GOPATH\bin}

go-torch

go-torch\text {go-torch}Uber\text {Uber}公司开源的一款针对 Golang\text {Golang} 程序的火焰图生成工具,能收集 stack traces\text {stack traces},并把它们整理成火焰图,直观地呈现给开发人员。

cd\text {cd}src\ch17,执行

go get -u github.com/uber/go-torch && go install github.com/uber/go-torch@latest

​src\ch17\tools

prof.go

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package main

import (
"log"
"math/rand"
"os"
"runtime/pprof"
"time"
)

const (
col = 10000
row = 10000
)

// fillMatrix 用随机数填充二维数组
func fillMatrix(m *[row][col]int) {
s := rand.New(rand.NewSource(time.Now().UnixNano()))

for i := 0; i < row; i++ {
for j := 0; j < col; j++ {
m[i][j] = s.Intn(100000)
}
}
}

// calculate 计算二维数组每行的和
func calculate(m *[row][col]int) {
for i := 0; i < row; i++ {
tmp := 0
for j := 0; j < col; j++ {
tmp += m[i][j]
}
}
}

func main() {
// CPU Profile 输出文件
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal("Could not create CPU profile: ", err)
}
defer f.Close()

// CPU Profile
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("Could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()

x := [row][col]int{}
fillMatrix(&x)
calculate(&x)

// Memory Profile 输出文件
f1, err := os.Create("mem.prof")
if err != nil {
log.Fatal("Could not create memory profile: ", err)
}
defer f1.Close()

// runtime.GC()

// Memory Profile
if err := pprof.WriteHeapProfile(f1); err != nil {
log.Fatal("Could not write memory profile: ", err)
}

// Goroutine Profile 输出文件
f2, err := os.Create("goroutine.prof")
if err != nil {
log.Fatal("Could not create goroutine profile: ", err)
}
defer f2.Close()

// Goroutine Profile
if gProf := pprof.Lookup("goroutine"); gProf == nil {
log.Fatal("Could not write goroutine profile: ")
} else {
gProf.WriteTo(f2, 0)
}
}
  1. cd\text {cd}src\ch17\tools,执行

go build prof.go

  1. 生成可执行文件 prof.exe\text {prof.exe},执行

.\prof.exe

  1. 生成相关的 prof\text {prof} 文件:cpu.prof\text {cpu.prof}mem.prof\text {mem.prof}goroutine.prof\text {goroutine.prof},执行

go tool pprof prof cpu.prof

依次输入toplist XXX查看详细情况:

分析:

top:展示了运行时间最长的 1010 个函数,以及运行时间占总运行时间的百分比和累积时间。

指标 说明
flat\text {flat} 函数自身 cpu\text {cpu} 运行时间
\text {flat%} 函数自身运行时间占整个程序 cpu\text {cpu} 运行时间中所占的百分比
\text {sum%} 函数自身运行时间以及它调用的其他函数的运行时间占整个程序 cpu\text {cpu} 运行时间的累积百分比
cum\text {cum} 函数自身运行时间以及它调用的其他函数的 cpu\text {cpu} 运行时间的累积时间
\text {cum%} 函数自身运行时间以及它调用的其他函数的运行时间占整个程序 cpu\text {cpu} 运行时间的累积百分比

list fillMatrix

指标 说明
flat\text {flat} 函数自身 cpu\text {cpu} 运行时间
cum\text {cum} 函数自身运行时间以及它调用的其他函数的 cpu\text {cpu} 运行时间的累积时间
$\text