特点

可以高效编译、支持高并发、面向垃圾回收

  1. 秒级完成大型程序的单节点编译
  2. 依赖管理清晰
  3. 不支持继承
  4. 支持垃圾回收、支持并发执行、支持多线程通讯
  5. 多核计算机支持友好

特性来源

image-20220129135408349

环境变量

Env Desc Value
GOARCH The architecture, or processor, for which to compile code. amd64
GOBIN The directory where ‘go install’ will install a command.
GOCACHE The directory where the go command will store cached information for reuse in future builds. ~/Library/Caches/go-build
GOMODCACHE The directory where the go command will store downloaded modules. ~/go/pkg/mod
GOENV The location of the Go environment configuration file. ~/Library/Application Support/go/env
GOOS The operating system for which to compile code. darwin
GOPATH The Go path is used to resolve import statements.
The GOPATH environment variable lists places to look for Go code.
~/go
The src directory holds source code.
The pkg directory holds installed package objects. – pkg/GOOS_GOARCH
The bin directory holds compiled commands.
GOPROXY URL of Go module proxy. https://proxy.golang.org,direct
GOSUMDB The name of checksum database to use and optionally its public key and URL. sum.golang.org
GOROOT The root of the go tree. /usr/local/Cellar/go/1.17.6/libexec
GOTMPDIR The directory where the go command will write temporary source files, packages, and binaries.
GOVCS Lists version control commands that may be used with matching servers.
GOEXE The executable file name suffix (“.exe” on Windows, “” on other systems).
GOHOSTARCH The architecture (GOARCH) of the Go toolchain binaries. amd64
GOHOSTOS The operating system (GOOS) of the Go toolchain binaries. darwin
GOTOOLDIR The directory where the go tools (compile, cover, doc, etc…) are installed. /usr/local/Cellar/go/1.17.6/libexec/pkg/tool/darwin_amd64
GOVERSION The version of the installed Go tree, as reported by runtime.Version. go1.17.6

基本命令

Command Desc
bug start a bug report
build compile packages and dependencies
clean remove object files and cached files
doc show documentation for package or symbol
env print Go environment information
fix update packages to use new APIs
fmt gofmt (reformat) package sources
generate generate Go files by processing source
get add dependencies to current module and install them
install compile and install packages and dependencies
list list packages or modules
mod module maintenance
run compile and run Go program
test test packages
tool run specified go tool
version print Go version
vet report likely mistakes in packages

build

  1. Go 不支持动态链接,编译时会将所有依赖编译进同一个二进制文件
  2. Go 支持交叉编译
1
$ GOOS=linux GOARCH=amd64 go build

test

  1. Go 原生自带测试
  2. go test 会扫描所有*_test.go结尾的文件,惯例:将测试代码与正式代码放在同一目录
1
2
3
4
5
6
7
package main

import "testing"

func TestAdd(t *testing.T) {
t.Log("Start Test")
}
1
2
3
4
5
6
$ go test -v simple_test.go 
=== RUN TestAdd
simple_test.go:6: Start Test
--- PASS: TestAdd (0.00s)
PASS
ok command-line-arguments 0.122s

常用数据结构

Const & Var

函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用

1
2
const identifier type
var identifier type
1
2
3
4
5
6
// a := 1 // non-declaration statement outside function body
var a = 1

func main() {
fmt.Println(a)
}

Slice

  1. 切片是对数组一个连续片段的引用
  2. 切片是连续内存并且可以动态扩展
1
2
3
4
5
6
7
8
9
10
11
func main() {
a := [5]int{1, 2, 3, 4, 5} // 数组
fmt.Println(a[1:3]) // [2 3]
fmt.Println(a[:]) // [1 2 3 4 5]
fmt.Println(deleteItem(a[:], 1)) // [1 3 4 5]
}

func deleteItem(s []int, index int) []int {
// ...操作符:解压缩切片
return append(s[:index], s[index+1:]...)
}

追加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
var a []int
printSlice(a) // pointer: 0x0, len: 0, cap: 0

b := []int{1, 2, 3}
printSlice(b) // pointer: 0xc0000160c0, len: 3, cap: 3

c := a
printSlice(c) // pointer: 0x0, len: 0, cap: 0

// a与c指向的内存地址不一样
// 最佳实践:x = append(x, ...)
a = append(b, 4)
printSlice(a) // pointer: 0xc000018180, len: 4, cap: 6
printSlice(c) // pointer: 0x0, len: 0, cap: 0
}

func printSlice(slice []int) {
fmt.Printf("pointer: %p, len: %d, cap: %d\n", slice, len(slice), cap(slice))
}

修改

Go是值传递

1
2
3
4
5
6
7
8
9
10
11
12
slice := []int{10, 20, 30, 40, 50}

for _, v := range slice {
// v是临时变量,值传递
v *= 2
}
fmt.Println(slice) // [10 20 30 40 50]

for index := range slice {
slice[index] *= 2
}
fmt.Println(slice) // [20 40 60 80 100]

Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
myMap := make(map[string]string, 10)
myMap["name"] = "zhongmingmao"

myFuncMap := map[string]func() string{
"getName": func() string { return "zhongmingmao" },
}
fmt.Println(myFuncMap) // map[getName:0x108a820]
getName := myFuncMap["getName"]
fmt.Println(getName()) // zhongmingmao

if v, exists := myMap["name"]; exists {
fmt.Println(v) // zhongmingmao
}
for k, v := range myMap {
fmt.Println(k, v) // name zhongmingmao
}

Interface & Struct & Pointer

  1. Go 支持指针,但不支持指针运算
  2. 指针变量的值为内存地址,未赋值的指针为nil
  3. interface 只能包含行为,struct 只能包含属性
  4. Duck Type:struct 无需显式声明实现 interface,只需直接实现方法
    • struct 除实现 interface 定义的接口外,还可以有额外的方法
  5. 一个类型可以实现多个接口(多重继承
  6. interface 可以嵌套其他 interface
  7. interface 可能为 nil,针对 interface 的使用要判空;struct 初始化意味着空间分配,对 struct 的引用不会出现空指针
  8. Go 只有一种规则:传递
    • 函数内修改参数的值不会影响函数外原始变量的值
    • 可以通过传递指针参数变量地址传递给调用函数
      • 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
type IF interface { // 只有行为
getName() string
}
type Human struct { // 只有属性
firstName, lastName string
}
type Car struct { // 只有属性
factory, model string
}

// Duck Type
func (h *Human) getName() string {
// 指针
return h.firstName + "," + h.lastName
}
func (c Car) getName() string {
// 结构体
return c.factory + "-" + c.model
}

func main() {
interfaces := []IF{} // 切片

h := new(Human) // new返回指针
h.firstName = "zhongming"
h.lastName = "mao"
interfaces = append(interfaces, h)

c := new(Car)
c.factory = "benz"
c.model = "s"
interfaces = append(interfaces, c)
for _, v := range interfaces {
fmt.Println(v.getName())
}
// zhongming,mao
// benz-s
}
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 ParameterStruct struct {
Name string
}

func changeParameter(param *ParameterStruct, v string) *ParameterStruct {
// 值传递:值为指针值,两个指针指向同一份内存地址
// 拷贝指针,会影响外部
param.Name = v
return param
}
func canNotChangeParameter(param ParameterStruct, v string) *ParameterStruct {
// 值传递:值为结构体,分配新的内存
// 拷贝结构体,不会影响外部
param.Name = v
return &param
}

func main() {
a := "a"
p := &a
println(a) // a
println(p) // 0xc000068ee0

b := *&a // 值传递,分配新的内存地址
println(b) // a
println(&b) // 0xc000068ed0

a = "b" // 内存地址没有发生变化
println(a) // b
println(&a) // 0xc000068ee0

b = "b" // 内存地址没有发生变化
println(b) // b
println(&b) // 0xc000068ed0

param := ParameterStruct{Name: "aaa"}
fmt.Println(param) // {aaa}

p1 := changeParameter(&param, "bbb")
fmt.Printf("%v, %v\n", p1 == &param, param) // true, {bbb}

p2 := canNotChangeParameter(param, "ccc")
fmt.Printf("%v, %v\n", p2 == &param, param) // false, {bbb}
}

Struct Tag

  1. 使用场景:Kubernetes APIServer 对所有资源的定义都用 json tagprotobuf tag
  2. 借助反射机制:Kubernetes YAML -> JSON -> Go Struct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type MyType struct {
Name string `json:"name"`
}

func main() {
mt := MyType{Name: "zhongmingmao"}
fmt.Printf("%T\n", mt) // main.MyType

myType := reflect.TypeOf(mt)
fmt.Printf("%T\n", myType) // *reflect.rtype

field := myType.Field(0)
fmt.Printf("%T\n", field) // reflect.StructField

tag := field.Tag
fmt.Printf("%T\n", tag) // reflect.StructTag

json := tag.Get("json")
fmt.Printf("%T, %s\n", json, json) // string, field
}

Type Alias

1
2
3
4
5
6
7
8
type ServiceType string

const (
ServiceTypeClusterIP ServiceType = "ClusterIP"
ServiceTypeNodePort ServiceType = "NodePort"
ServiceTypeLoadBalancer ServiceType = "LoadBalancer"
ServiceTypeExternalName ServiceType = "ExternalName"
)

Make & New

  1. New 返回指针地址
  2. Make 返回第一个元素,可预设内存空间,避免未来的内存拷贝
1
2
3
4
fmt.Printf("%T\n", new([]int))          // *[]int
fmt.Printf("%T\n", make([]int, 0)) // []int
fmt.Printf("%T\n", make([]int, 10)) // []int
fmt.Printf("%T\n", make([]int, 10, 20)) // []int

函数

Main 函数

  1. 每个 Go 程序都应该有一个 main package
  2. main package 里的 main 函数是 GO 程序入口
1
2
3
4
5
package main

func main() {
println("Hello World")
}

参数解析

os.Args

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

import (
"fmt"
"os"
)

func main() {
// [/private/var/folders/74/cz6f8rf54xx0z41vg1bw4g280000gn/T/GoLand/___go_build_cnf]
fmt.Println(os.Args)
}

flag

1
2
3
4
5
6
var name = flag.String("name", "zhongmingmao", "Input Your Name")
var age int
flag.IntVar(&age, "age", 0, "Input Your Age")

flag.Parse()
fmt.Println(*name, age)
1
2
3
4
# go build main.go

# ./main -name=y --age 99
y 99

Init 函数

  1. init 函数:在包初始化时运行
  2. 谨慎使用 init 函数
    • 当多个项目引用同一项目,且被引用的项目的初始化在 init 函数中完成,并且不可重复运行时,会导致启动错误
init/a/init.go
1
2
3
4
5
6
7
8
9
package a

import (
_ "init/b"
)

func init() {
println("init from a")
}
init/b/init.go
1
2
3
4
5
package b

func init() {
println("init from b")
}
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
_ "init/a"
_ "init/b"
)

var v = 0

func init() {
println("init from main")
v = 1
}

func main() {
println("main", v)
// init from b
// init from a
// init from main
// main 1
}

返回值

  1. 多值返回:函数可以返回任意数量的返回值
  2. 命名返回值
    • 返回值可被命名,会被视为定义在函数顶部的变量
    • 使用没有参数的 return 语句
    • 调用者可以忽略部分返回值
1
2
3
4
5
6
7
8
9
10
11
12
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
// 没有参数的 return 语句,返回命名返回值:x, y
return
}

func main() {
// 忽略部分返回值
x, _ := split(17)
fmt.Println(x) // 7
}

变长参数

1
func append(slice []Type, elems ...Type) []Type
1
2
s := []string{}
s = append(s, "a", "b", "c")

内置函数

函数名 作用
close 关闭 Channel
len cap 返回长度或者容量:Array、Slice、Map
new make 内存分配
copy append 操作切片
panic recover 错误处理
print println 打印
complex real imag 操作复数

回调函数

函数作为参数传入其他函数,并在其它函数内部进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
DoOperation(10, increase) // 11
DoOperation(10, decrease) // 9
}

func DoOperation(y int, f func(int, int)) {
f(y, 1)
}

func increase(a, b int) {
println(a + b)
}

func decrease(a, b int) {
println(a - b)
}

闭包

  1. 闭包的能力:可以在一个内层函数中访问到其外层函数的作用域
  2. 闭包 = 匿名函数 !!
    • 不能独立存在
    • 可以赋值给其它变量x := func() {}
    • 可以直接调用: func(x, y int) { println(x + y) }(1, 2)
    • 可以作为函数返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
// 返回的函数直接调用
println(increase()(1)) // 4
}

// 匿名函数作为函数返回值
func increase() func(x int) int {
delta := 3
return func(x int) int {
// 访问外层 delta
return x + delta
}
}

方法

  1. 方法的定义:作用在接收者上的函数
  2. 使用场景
    • 函数需要的上下文可以保存在 receiver 的属性
    • 通过定义 receiver 的方法,该方法可以直接访问 receiver 的属性,减少参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Server struct {
URL string
}

func (receiver *Server) StartTLS() {
if receiver.URL != "" {
panic("Server started already!")
}
}

func main() {
server := Server{URL: "zhongmingmao.me"}
server.StartTLS()
}

反射

  1. reflect.TypeOf():返回被检查对象的类型
  2. reflect.ValueOf():返回被检查对象的值
1
2
3
4
5
6
7
8
9
10
11
12
m := make(map[string]string, 10)
m["name"] = "zhongmingmao"

// func TypeOf(i interface{}) Type
// type Type interface
t := reflect.TypeOf(m)
fmt.Printf("%T, %s\n", t, t) // *reflect.rtype, map[string]string

// func ValueOf(i interface{}) Value
// type Value struct
v := reflect.ValueOf(m)
fmt.Printf("%T, %s\n", v, v) // reflect.Value, map[name:zhongmingmao]
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
type student struct {
name string
age int
}

func (receiver student) GetName() string {
return receiver.name
}

func (receiver student) GetAge() int {
return receiver.age
}

func main() {
s := student{name: "A", age: 1}

t := reflect.TypeOf(s)
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("Method[%d]: %T, %v\n", i, method, method.Name)
}
// Method[0]: reflect.Method, GetAge
// Method[1]: reflect.Method, GetName

v := reflect.ValueOf(s)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fmt.Printf("Field[%d]: %T, %v\n", i, field, field)
}
// Field[0]: reflect.Value, A
// Field[1]: reflect.Value, 1
}

JSON

json包使用 map[string]interface{}[]interface{} 保存任意对象

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
type Student struct {
Name string `json:"name"`
sex string
Class *Class `json:"class"`
}

type Class struct {
Name string
}

func main() {
class := new(Class)
class.Name = "c"

student := Student{
Name: "z",
sex: "male",
Class: class,
}

if bytes, err := json.Marshal(student); err == nil {
fmt.Println(string(bytes)) // {"name":"z","class":{"Name":"c"}}

// 解析任意对象
var obj interface{}
if err := json.Unmarshal(bytes, &obj); err == nil {
// Type assertions: v, ok = x.(T)
// If T is not an interface type, x.(T) asserts that the dynamic type of x is identical to the type T.
// If T is an interface type, x.(T) asserts that the dynamic type of x implements the interface T.
if m, ok := obj.(map[string]interface{}); ok {
for key, v := range m {
// Type switches
switch value := v.(type) {
case string:
fmt.Printf("key: %v, value: %v, type: %T\n", key, value, value)
case interface{}:
fmt.Printf("key: %v, value: %v, type: %T\n", key, value, value)
default:
fmt.Printf("key: %v, value: %v, type: %T\n", key, value, value)
}
}
// key: name, value: z, type: string
// key: class, value: map[Name:c], type: map[string]interface {}
}
}
}
}

面向对象

特性 描述
封装 public为大写,非大写只能在包内使用
继承 不支持继承,通过组合实现
多态 Duck Type

异常

Error

  1. Go 无内置 exception 机制,只提供 error 接口定义错误
  2. 可以通过 errors.New 或者 fmt.Errorf 创建新的 error
  3. 通常应用程序对 error 的处理:判空
    • 如需对 error 归类,通常交给应用程序自定义
1
2
3
type error interface {
Error() string
}
1
2
3
e1 := errors.New("NotFound")
e2 := fmt.Errorf("error_%d", 1)
println(e1.Error(), e2.Error()) // NotFound error_1

Defer

  1. 作用:函数返回前执行某个语句或者函数,等同于 Java 的 finally
  2. 使用场景:关闭打开的资源
  3. Defer 的执行顺序类似于:先定义后执行
1
2
3
4
5
6
func main() {
defer print(1)
defer print(2)
defer print(3)
// 321
}
1
2
3
4
5
6
7
8
9
lock := sync.Mutex{}
for i := 0; i < 3; i++ {
i := i
go func() { // 使用闭包解决死锁,保证 Go Routine 结束的时候执行 defer 里面的解锁逻辑
defer lock.Unlock()
lock.Lock()
println(i)
}()
}

Panic & Recover

  1. panic
    • 在系统出现不可恢复错误主动调用 panic,使当前线程直接 Crash
  2. defer
    • 保证执行,并把控制权交还给接收到 panic 的函数调用者
  3. recover
    • 函数从 panic 或者错误场景中恢复
1
2
3
4
5
6
7
8
9
10
// defer + recover
defer func() {
fmt.Println("defer func is called")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("a panic is triggered")
// defer func is called
// a panic is triggered

并发

并发 & 并行

  1. 并发(单核):多个事件间隔发生
  2. 并行(多核):多个事件同时发生

协程

  1. 进程
    • 分配系统资源(CPU 时间、内存等)的基本单位
    • 有独立的内存空间、切换开销大
  2. 线程
    • 线程是进程的一个执行流,是 CPU 调度并能独立运行的基本单位
    • 同一进程中的多线程共享内存空间,线程切换代价小
    • 线程间通信比进程间通信更方便
    • 从内核层面来看,线程其实也是一种特殊的进程
      • 父进程共享了打开的文件文件系统信息,共享了地址空间信号处理函数
  3. 协程
    • 协程是 Go 中的轻量级线程实现
    • Go 在 Runtime系统调用等多方面对 goroutine 调度进行了封装和处理,从语言层面支持了协程
      • 当遇到长时间执行或者系统调用时,会主动把当前 goroutine 的 CPU 转让出去

CSP

  1. CSP
    • Communicating Sequential Process
      • Communicating:Routine + Channel
      • Sequential:调度器
    • 描述两个独立的并发实体通过共享的通讯管道 Channel 进行通信的并发模型
      • 在 Go 中,独立的并发实体即 goroutine
  2. goroutine
    • 轻量级线程,并非 OS 的线程,而是将 OS 的线程分段使用,通过调度器实现协作式调度
    • goroutine 是一种绿色线程,微线程,能够在发现阻塞启动新的微线程
      • 绿色线程是一种由 Runtime 或 VM 调度,而不是由 OS 调度的线程
  3. channel
    • 类似于 Unix 的 Pipe,用于协程之间的通讯和同步
    • goroutine 之间虽然解耦,但 goroutine 与 channel 有耦合

Thread & Routine

Thread Routine
占用内存 8MB 2KB
切换开销 大,涉及模式切换,涉及16个寄存器的刷新 小,涉及3个寄存器的刷新
并发数量 比较有限 GOMAXPROC

Go Routine

1
2
3
for i := 0; i < 10; i++ {
go fmt.Println(i) // 启动新协程
}

Channel

  1. Channel
    • Channel 是多个 goroutine 之间通讯的管道
      • 一端发送数据,一端接收数据
    • 同一时间只有一个 goroutine 可以访问数据,避免了共享内存模式可能出现的内存竞争
      • 调度器:协调 goroutine 的执行顺序
    • 基于 Channel 的通信是同步
  2. 声明方式
    • var identifier chan datatype
    • 操作符:<-

Unbuffered channel

1
2
3
4
5
6
7
8
9
10
11
12
// 默认缓冲区容量为 0,即 unbuffered channel,类似于 Java 的 SynchronousQueue
ch := make(chan int)
go func() {
println("write data to channel")
ch <- 0 // 数据写入 channel
}()

println("read data from channel")
println(<-ch) // 从 chanel 中读取数据
// read data from channel
// write data to channel
// 0

Buffered Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ch := make(chan int, 2)

go func() {
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
}()

for v := range ch {
fmt.Println(v)
}
// 0
// 1
// 2

One-way Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
twoWayChan := make(chan int, 10)
go produce(twoWayChan)
go consume(twoWayChan)
}

func produce(writeOnlyChan chan<- int) {
for {
writeOnlyChan <- 1
}
}

func consume(readOnlyChan <-chan int) {
for {
<-readOnlyChan
}
}

Close Channel

  1. Channel 无需每次关闭
  2. 关闭 Channel 的作用:通知接收方该 Channel 再无新数据发送
  3. 只有发送方需要关闭 Channel
1
2
3
4
5
6
7
8
9
10
ch := make(chan int, 1)

defer func() {
close(ch)
}()

ch <- 1
if v, notClosed := <-ch; notClosed {
println(v) // 1
}

Select

  1. 当多个 Routine 同时运行,可通过 Select 轮询多个通道
  2. 规则
    • 如果所有 Channel 都阻塞等待,如果定义了 default 则执行 default
    • 如果多个 Channel 就绪随机选择
1
2
3
4
5
6
7
8
9
10
11
12
13
ch1 := make(chan int, 1)
ch2 := make(chan string, 1)
ch1 <- 1
ch2 <- "a"

select {
case v := <-ch1:
fmt.Printf("select from ch1, v: %v\n", v)
case v := <-ch2:
fmt.Printf("select from ch2, v: %v\n", v)
default:
fmt.Println("select default")
}

Timeout Channel

  1. time.Ticker 以指定的时间间隔重复地向 Channel C 发送时间值
  2. 使用场景:为 Routine 设定超时时间
1
2
3
4
5
6
7
8
9
ch := make(chan int)
timer := time.NewTimer(time.Second)
select {
case <-ch:
fmt.Println("received from ch")
case <-timer.C: // When the Timer expires, the current time will be sent on C, which C is (<-chan Time)
fmt.Println("timeout waiting from channel ch")
}
// timeout waiting from channel ch

Stop Routine

Done Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
messages := make(chan int, 10)
defer close(messages)

// consumer
done := make(chan bool)
go func() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
select {
case <-done:
println("child process interrupt...")
return // 退出 Routine
default:
fmt.Printf("receive message: %d\n", <-messages)
}
}
}()

// producer
for i := 0; i < 10; i++ {
messages <- i
}

time.Sleep(3 * time.Second)
close(done)
time.Sleep(time.Second)
println("main process exit!")

// receive message: 0
// receive message: 1
// child process interrupt...
// main process exit!

Context

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
baseCtx := context.Background()
valuedCtx := context.WithValue(baseCtx, "name", "zhongmingmao")

go func(c context.Context) {
fmt.Println(c.Value("name")) // zhongmingmao
}(valuedCtx)

timeoutCtx, cancel := context.WithTimeout(baseCtx, time.Second)
defer cancel()
go func(ctx context.Context) {
ticker := time.NewTicker(time.Second)
for range ticker.C {
select {
case <-ctx.Done(): // 超时后会触发
println("child process interrupt...")
return
default:
println("enter default")
}
}
}(timeoutCtx)

select {
case <-timeoutCtx.Done():
time.Sleep(time.Second)
println("main process exit!")
}
// enter default
// child process interrupt...
// main process exit!