视频学习链接:https://www.bilibili.com/video/BV1gf4y1r79E/?p=1&vd_source=21e06970a09504e227f7f3380f7cb09c

Golang学习笔记

Golang简介

Golang的优势

Golang优势

静态类型语言

语言层面的并发

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

import (
"fmt"
"time"
)

func goFunc(i int) {
fmt.Println("goroutine ", i, " ...")
}

func main() {
for i := 0; i < 10000; i++ {
go goFunc(i) //开启一个并发协程
}

time.Sleep(time.Second)
}

强大的标准库

标准库

简单易学

大厂领军

Golang适合做什么

(1)、云计算基础设施领域

代表项目:docker、kubernetes、etcd、consul、cloudflare CDN、七牛云存储等。

(2)、基础后端软件

代表项目:tidb、influxdb、cockroachdb等。

(3)、微服务

代表项目:go-kit、micro、monzo bank的typhon、bilibili等。

(4)、互联网基础设施

代表项目:以太坊、hyperledger等。

Golang明星作品

明星作品docker

明星作品k8s

GitHub点赞

Golang的不足

  1. 包管理,大部分都在GitHub
  2. 无泛化类型(Golang 1.18+已经支持泛型)
  3. 所有Excepiton都用Error来处理(比较有争议)
  4. C的降级处理,并非无缝,没有C降级到asm那么完美(序列化问题)

Golang语法

第一个Golang程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main // 程序的包名

/*
import "fmt"
import "time"
*/

// 当要导入多个包时,推荐此方式
import (
"fmt",
"time"
)

// main函数
func main() { // 函数的 "{" 一定是和函数名在同一行的,否则编译错误
// Golang中的表达式,加";",和不加都可以,建议不加
fmt.Println("Hello Go!")
time.Sleep(1 * time.Second)
}

终端运行

1
2
PS D:\Go_Project> go run hello.go
Hello Go!

go run 表示 直接编译go语言并执行应用程序,一步完成

也可以先编译,再运行

1
PS D:\Go_Project> go build hello.go

此时在项目中生成可执行文件

Go1

1
2
PS D:\Go_Project> ./hello
Hello Go!
  • 第一行代码package main定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package mainpackage main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
  • **import “fmt”**告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
  • func main()是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。

注意:这里面go语言的语法,定义函数的时候,‘{’ 必须和函数名在同一行,不能另起一行。

  • /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 / 开头,并以 / 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
  • fmt.Println(...)可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 PrintPrintln 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。

变量的声明

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
package main

import (
"fmt"
)

// 声明全局变量,方法一、二、三是可以的
var gA int
var gB int = 100
var gC = 200

// 用方法四来声明全局变量
// := 只能在函数体内声明
// gD := 100

func main() {
// 方法一:声明一个变量,默认值是0
var a int
fmt.Println("a = ", a) // a = 0
fmt.Printf("type of a = %T\n", a) // type of a = int

// 方法二:声明一个变量,初始化一个值
var b int = 100
fmt.Println("b = ", b) // b = 100
fmt.Printf("type of b = %T\n", b) // type of b = int

var bb string = "abc"
fmt.Printf("bb = %s, type of bb = %T\n", bb, bb) // bb = abc, type of bb = string

// 方法三:在初始化的时候,可以省去数据类型,通过值自动匹配当前变量的数据类型
var c = 100
fmt.Println("c = ", c) // c = 100
fmt.Printf("type of c = %T\n", c) // type of c = int

var cc = "abc"
fmt.Printf("cc = %s, type of cc = %T\n", cc, cc) // cc = abc, type of cc = string

// 方法四:(常用)省去var关键字,直接自动匹配
d := 100
fmt.Println("d = ", d) // d = 100
fmt.Printf("type of d = %T\n", d) // type of d = int

e := "abc"
fmt.Println("e = ", e) // e = abc
fmt.Printf("type of e = %T\n", e) // type of e = string

pi := 3.14
fmt.Println("pi = ", pi) // pi = 3.14
fmt.Printf("type of pi = %T\n", pi) // type of pi = float64

fmt.Println("===============================")
fmt.Println("gA = ", gA, ", gB = ", gB, ",gC = ", gC)
// fmt.Println("gD = ", gD)

// 声明多个变量
var xx, yy int = 100, 200
fmt.Println("xx = ", xx, ", yy = ", yy) // xx = 100 , yy = 200
var kk, ll = 100, "byu_rself"
fmt.Println("kk = ", kk, ", ll = ", ll) // kk = 100 , ll = byu_rself

// 多行的多变量声明
var (
vv int = 100
jj bool = true
)
fmt.Println("vv = ", vv, ", jj = ", jj) // vv = 100 , jj = true
}

常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

// 用const来定义枚举类型
const (
// 可在const()添加一关键字 iota,每行的iota都会累加1,第一行的iota的默认值是0
BEIJING = iota // iota = 0
SHANGHAI // iota = 1
NINGBO // iota = 2
)

const (
a, b = iota + 1, iota + 2 // iota = 0,a = iota + 1,b = iota + 2, a = 1, b = 2
c, d // iota = 1,c = iota + 1,d = iota + 2, c = 2, d = 3
e, f // iota = 2,e = iota + 1,f = iota + 2, e = 3, f = 4
g, h = iota * 2, iota * 3 // iota = 3,g = iota * 2,h = iota * 3, g = 6, h = 9
i, k // iota = 4,i = iota * 2,k = iota * 3, i = 8, k = 12
)

func main() {
// 常量(只读属性)
const length int = 10
fmt.Println("length = ", length)

// length = 100 常量是不允许修改的
fmt.Println("BEIJING = ", BEIJING)
fmt.Println("SHANGHAI = ", SHANGHAI)
fmt.Println("NINGBO = ", NINGBO)

// iota只能配合const()一起使用
// var a int = iota
}

输出

length = 10
BEIJING = 0
SHANGHAI = 1
NINGBO = 2

iota乘$10$后

1
2
3
4
5
6
const (
// 可在const()添加一关键字 iota,每行的iota都会累加1,第一行的iota的默认值是0
BEIJING = 10 * iota // iota = 0
SHANGHAI // iota = 1
NINGBO // iota = 2
)

输出

length = 10
BEIJING = 0
SHANGHAI = 10
NINGBO = 20

注意iota“每行”的概念

1
2
3
4
5
6
7
const (
a, b = iota + 1, iota + 2 // iota = 0,a = iota + 1,b = iota + 2, a = 1, b = 2
c, d // iota = 1,c = iota + 1,d = iota + 2, c = 2, d = 3
e, f // iota = 2,e = iota + 1,f = iota + 2, e = 3, f = 4
g, h = iota * 2, iota * 3 // iota = 3,g = iota * 2,h = iota * 3, g = 6, h = 9
i, k // iota = 4,i = iota * 2,k = iota * 3, i = 8, k = 12
)

总结

  1. const为只读属性,可以用const来定义枚举类型
  2. iota只能配合const()一起使用
  3. 每行iota都会累加1,第一行的iota的默认值是0

函数

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
package main

import "fmt"

// 单个返回值
func f1(a string, b int) int {
fmt.Println("a = ", a)
fmt.Println("b = ", b)

c := 100
return c
}

// 匿名返回多个值
func f2(a string, b int) (int, int) {
fmt.Println("a = ", a)
fmt.Println("b = ", b)

return 111, 222
}

// 返回多个返回值,有形参名的
func f3(a string, b int) (r1 int, r2 int) {
fmt.Println("a = ", a)
fmt.Println("b = ", b)

// r1, r2属于f3的形参,初始化默认值为0
// r1, r2作用域空间为f3整个函数体{}空间
fmt.Println("赋值之前: r1 = ", r1, ", r2 = ", r2)

// 给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return

// 也可以直接返回数据
// return 666, 777
}

// 当返回的形参的类型相同时,可同时定义类型
func f4(a string, b int) (r1, r2 int) {
fmt.Println("a = ", a)
fmt.Println("b = ", b)

// 给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return
}

func main() {
c := f1("abc", 123)
fmt.Println("c = ", c)

ret1, ret2 := f2("hello", 666)
fmt.Println("ret1 = ", ret1, ", ret2 = ", ret2)

ret1, ret2 = f3("f3", 333)
fmt.Println("ret1 = ", ret1, ", ret2 = ", ret2)
}

输出

a = abc
b = 123
c = 100
a = hello
b = 666
ret1 = 111 , ret2 = 222
a = f3
b = 333
赋值之前: r1 = 0 , r2 = 0
ret1 = 1000 , ret2 = 2000

init()函数与import

init 函数可在package main中,可在其他package中,可在同一个package中出现多次。

main函数只能在package main中。

执行顺序

Golang里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。

虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数

Go程序会自动调用init()main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数

程序的初始化和执行都起始于main包。

如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。

当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。

等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:

init

例子演示

Golang中,函数的首字母为大写时,则方法为public方法,首字母为小写时,方法为private方法。

目录结构

lib1.go

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

import "fmt"

func Lib1Test() {
fmt.Println("lib1Test()...")
}

func init() {
fmt.Println("lib1.init()...")
}

lib2.go

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

import "fmt"

func Lib2Test() {
fmt.Println("lib2Test()...")
}

func init() {
fmt.Println("lib2.init()...")
}

main.go

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

import (
"5-init/lib1"
"5-init/lib2"
)

func main() {
lib1.Lib1Test()
lib2.Lib2Test()
}

输出结果

1
2
3
4
lib1.init()...
lib2.init()...
lib1Test()...
lib2Test()...

修改lib1.go,使其导入lib2.go

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

import (
_ "5-init/lib2" // "_" 匿名导包,导入后可以不使用
"fmt"
)

func Lib1Test() {
fmt.Println("lib1Test()...")
}

func init() {
fmt.Println("lib1.init()...")
}

同时修改main.go

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

import (
"5-init/lib1"
// "5-init/lib2"
)

func main() {
lib1.Lib1Test()
// lib2.Lib2Test()
}

输出结果

1
2
3
lib2.init()...
lib1.init()...
lib1Test()...

分析:main函数需要导入lib1,而lib1又需要导入lib2,因此先加载lib2,执行lib2的init函数后,再加载lib1,执行lib1的init函数,最后在main函数中调用方法

导入的包不仅可以匿名,也可以取别名

1
2
3
4
5
6
7
8
9
package main

import (
mylib1 "5-init/lib1" // 将导入的lib1取别名mylib1
)

func main() {
mylib1.Lib1Test() // 通过mylib1调用方法
}

也可通过.,使得调用方法时无需输入包名(不推荐,容易引起歧义)

1
2
3
4
5
6
7
8
9
package main

import (
. "5-init/lib1"
)

func main() {
Lib1Test() // 直接调用方法,不推荐使用此方法
}

指针

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

import "fmt"

// 值交换
func swap(a int, b int) {
var temp int = a
a = b
b = temp
}

func main() {
var a int = 10
b := 20

swap(a, b)

fmt.Println("a = ", a, ", b = ", b)
}

输出

1
a =  10 , b =  20

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
var a int = 10
fmt.Printf("变量的地址: %x\n", &a )
}

输出

1
变量的地址: 20818a220

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

以经典的交换函数swap()举例

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 main

import "fmt"

// 值交换
/* func swap(a int, b int) {
var temp int = a
a = b
b = temp
} */

// 引用交换
func swap(a *int, b *int) {
temp := *a
*a = *b
*b = temp
}

func main() {
var a int = 10
b := 20

swap(&a, &b)

fmt.Println("a = ", a, ", b = ", b)
}

输出

1
a =  20 , b =  10

加强对指针的理解

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

import "fmt"

func main() {
var a int = 10

var p *int = &a // 一级指针
fmt.Println(&a) // 0xc000018088
fmt.Println(p) // 0xc000018088

pp := &p // 二级指针
fmt.Println(&p) // 0xc00000a030
fmt.Println(pp) // 0xc00000a030
}

defer

defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数

defer作用:

  • 释放占用的资源
  • 捕捉处理异常
  • 输出日志

如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。

知识点一:defer的执行顺序

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

import "fmt"

func Demo(){
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
}

func main() {
Demo()
}

输出

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

import "fmt"

func main() {
// 写入defer关键字,类似压栈的形式 defer类似与Java中的finally关键字
defer fmt.Println("main end 1") // 先入栈
defer fmt.Println("main end 2") // 后入栈

fmt.Println("main:hello go 1")
fmt.Println("main:hello go 2")
}

输出

1
2
3
4
main:hello go 1
main:hello go 2
main end 2
main end 1

知识点二:defer和return谁先谁后

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

import "fmt"

func deferFunc() int {
fmt.Println("defer func called...")
return 0
}

func returnFunc() int {
fmt.Println("return func called...")
return 0
}

func returnAndDefer() int {
defer deferFunc()
return returnFunc()
}

func main() {
returnAndDefer()
}

输出

1
2
return func called...
defer func called...

总结

return之后的语句先执行,defer之后的语句后执行

slice

数组

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
package main

import "fmt"

func printArray(array [4]int) {
// 值拷贝
for index, value := range array {
fmt.Println("index = ", index, ", value = ", value)
}
array[0] = 111
}

func main() {
// 固定长度的数组
var array1 [10]int

for i := 0; i < len(array1); i++ {
fmt.Println(array1[i])
}

// 前4个元素为{1,2,3,4},后面均为0
array2 := [8]int{1, 2, 3, 4}
for index, value := range array2 {
fmt.Println("index = ", index, ", value = ", value)
}

// 查看数组的数据类型
fmt.Printf("array1 type = %T\n", array1)
fmt.Printf("array2 type = %T\n", array2)

array3 := [4]int{1, 2, 3, 4}
printArray(array3)
fmt.Println("==============================")
for index, value := range array3 {
fmt.Println("index = ", index, ", value = ", 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
0
0
0
0
0
0
0
0
0
0
index = 0 , value = 1
index = 1 , value = 2
index = 2 , value = 3
index = 3 , value = 4
index = 4 , value = 0
index = 5 , value = 0
index = 6 , value = 0
index = 7 , value = 0
array1 type = [10]int
array2 type = [8]int
index = 0 , value = 1
index = 1 , value = 2
index = 2 , value = 3
index = 3 , value = 4
==============================
index = 0 , value = 1
index = 1 , value = 2
index = 2 , value = 3
index = 3 , value = 4

切片

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

import "fmt"

func printArr(array []int) {
// 引用传递
// "_" 表示匿名变量值,当只关心数组的值,而不关心索引下标时,可以使用 "_"
for _, value := range array {
fmt.Println("value = ", value)
}
array[0] = 100
}

func main() {
array1 := []int{1, 2, 3, 4} // 动态数组,切片 slice
fmt.Printf("array1 type = %T\n", array1)
printArr(array1)
fmt.Println("=======================")
for _, value := range array1 {
fmt.Println("value = ", value)
}
}

输出

1
2
3
4
5
6
7
8
9
10
array1 type = []int
value = 1
value = 2
value = 3
value = 4
=======================
value = 100
value = 2
value = 3
value = 4

总结

  1. 数组的长度是固定的
  2. 固定长度的数组在传参的时候,是严格匹配数组类型的
  3. 数组在传值的时候是值拷贝,无法修改其原本数组
  4. slice(动态数组)在传参时是引用传递,且不同元素长度的动态数组的形参是一致的

slice的声明方式

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
package main

import "fmt"

func main() {
// 声明slice1是一个切片,并初始化默认值为1,2,3,长度len是3
slice1 := []int{1, 2, 3}
fmt.Printf("len = %d, slice = %v\n", len(slice1), slice1)

// 声明slice2是一个切片,但并没有给slice2分配空间
var slice2 []int
// slice2[0] = 1 // 未分配空间,报错:runtime error: index out of range [0] with length 0
fmt.Printf("len = %d, slice = %v\n", len(slice2), slice2)

slice2 = make([]int, 3) // 开辟3个空间
slice2[0] = 100
fmt.Printf("len = %d, slice = %v\n", len(slice2), slice2)

// 声明slice3是一个切片,同时给slice3分配3个空间,初始化值是0
// var slice3 []int = make([]int, 3)
slice3 := make([]int, 3) // 两种声明方式等价
fmt.Printf("len = %d, slice = %v\n", len(slice3), slice3)

// 判断slice是否为0
if slice3 == nil {
fmt.Println("slice3是一个空切片")
} else {
fmt.Println("slice3是有空间的")
}
}

切片容量的追加

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
package main

import "fmt"

func main() {
var numbers = make([]int, 3, 5)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
// numbers[4] = 4 // 报错:runtime error: index out of range [4] with length 3

// 向numbers切片添加一个元素4
numbers = append(numbers, 4)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

// 向numbers切片添加一个元素5
numbers = append(numbers, 5)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

// 向一个容量cap已经满的slice追加元素
numbers = append(numbers, 6) // 往cap已经满的slice追加元素后,slice会扩容 cap 个空间
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

fmt.Println("=========================================")
var numbers2 = make([]int, 3)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers2), cap(numbers2), numbers2)

numbers2 = append(numbers2, 1) // 长度超过cap,则将容量增加2倍
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers2), cap(numbers2), numbers2)
}

输出

1
2
3
4
5
6
7
len = 3, cap = 5, slice = [0 0 0]
len = 4, cap = 5, slice = [0 0 0 4]
len = 5, cap = 5, slice = [0 0 0 4 5]
len = 6, cap = 10, slice = [0 0 0 4 5 6]
=========================================
len = 3, cap = 3, slice = [0 0 0]
len = 4, cap = 6, slice = [0 0 0 1]

总结

  1. 切片的长度容量不同,长度表示左指针至右指针之间的距离,容量表示左指针至底层数组末尾的距离
  2. 切片的扩容机制append的时候,如果长度增加后超过容量,则将容量增加$2$倍

切片的截取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import "fmt"

func main() {
/* 创建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)

/* 打印原始切片 */
fmt.Println("numbers ==", numbers)

/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])

/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])

/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])

numbers1 := make([]int,0,5)
printSlice(numbers1)

/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)

/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

输出

1
2
3
4
5
6
7
8
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]

copy()函数

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

import "fmt"

func main() {
s := []int{1, 2, 3} // len = 3, cap =3, s = [1, 2, 3]
// 截取 s 数组中 [0, 2)中的元素
s1 := s[0:2] // s1 = [1, 2]
fmt.Println(s1)

s1[0] = 100
fmt.Println(s)
fmt.Println(s1)

// copy 可以将底层数组的slice一起进行拷贝
s2 := make([]int, 3) // s2 = [0, 0, 0]
// 将s中的值依次拷贝到s2中
copy(s2, s)
fmt.Println(s2)
}

输出

1
2
3
4
[1 2]
[100 2 3]
[100 2]
[100 2 3]

map

map的定义方式

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
package main

import "fmt"

func main() {
// 第一种声明方式
// 声明myMap1为map类型,key是string,value是string
var myMap1 map[string]string
if myMap1 == nil {
fmt.Println("myMap1是一个空map")
}
// 在使用map前,需要先用make给map分配数据空间
myMap1 = make(map[string]string, 10)
myMap1["one"] = "java"
myMap1["two"] = "python"
myMap1["three"] = "c++"
fmt.Println(myMap1)

// 第二种声明方式
myMap2 := make(map[int]string)
myMap2[1] = "java"
myMap2[2] = "python"
myMap2[3] = "c++"
fmt.Println(myMap2)

// 第三种声明方式
myMap3 := map[string]string{
"one": "java",
"two": "python",
"three": "c++",
}
fmt.Println(myMap3)
}

输出

1
2
3
4
myMap1是一个空map
map[one:java three:c++ two:python]
map[1:java 2:python 3:c++]
map[one:java three:c++ two:python]

map常用操作

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
package main

import "fmt"

func printMap(cityMap map[string]string) {
// 引用传递
for key, value := range cityMap {
fmt.Println("key = ", key, ", value = ", value)
}
}

func changeMap(cityMap map[string]string) {
// 引用传递
cityMap["England"] = "London"
}

func main() {
cityMap := make(map[string]string)

// 添加
cityMap["China"] = "Beijing"
cityMap["Japan"] = "Tokyo"
cityMap["USA"] = "NewYork"

// 遍历
for key, value := range cityMap {
fmt.Println("key = ", key, ", value = ", value)
}

// 删除
delete(cityMap, "China")

// 修改
cityMap["USA"] = "DC"

changeMap(cityMap)

fmt.Println("=====================================")

// 遍历
printMap(cityMap)
}

输出

1
2
3
4
5
6
7
key =  Japan , value =  Tokyo
key = USA , value = NewYork
key = China , value = Beijing
=====================================
key = USA , value = DC
key = England , value = London
key = Japan , value = Tokyo

面向对象

结构体

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
package main

import "fmt"

// 声明一种新的数据类型 myint,是 int 的一个别名
type myint int

// 定义一个结构体
type Book struct {
title string
auth string
}

func changeBook(book Book) {
// 传递一个book的副本
book.auth = "666"
}

func changeBook2(book *Book) {
// 指针传递
book.auth = "777"
}

func main() {
var a myint = 10
fmt.Println("a = ", a)
fmt.Printf("type of a = %T\n", a)

var book1 Book
book1.title = "Golang"
book1.auth = "zhangsan"
fmt.Printf("%v\n", book1)

changeBook(book1)
fmt.Printf("%v\n", book1)

changeBook2(&book1)
fmt.Printf("%v\n", book1)
}

输出

1
2
3
4
5
a =  10
type of a = main.myint
{Golang zhangsan}
{Golang zhangsan}
{Golang 777}

封装

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
package main

import "fmt"

type Hero struct {
Name string
Ad int
Level int
}

func (this Hero) Show() {
fmt.Println("Name = ", this.Name)
fmt.Println("Ad = ", this.Ad)
fmt.Println("Level = ", this.Level)
}

func (this Hero) GetName() string {
return this.Name
}

func (this Hero) SetName(newName string) {
this.Name = newName
}

func main() {
// 创建一个对象
hero := Hero{Name: "zhangsan", Ad: 100, Level: 10}
hero.Show()

hero.SetName("LiSi")
hero.Show()
}

输出

1
2
3
4
5
6
Name =  zhangsan
Ad = 100
Level = 10
Name = zhangsan
Ad = 100
Level = 10

将传入的对象修改为指针

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
package main

import "fmt"

// 如果类名首字母大写,表示该类可被其他包访问(类似Java的public)
type Hero struct {
// 如果类的属性的首字母大写,表示该属性对外能够访问(类似Java的public),否则只能类的内部访问(类似Java的private)
Name string
Ad int
Level int
}

/* func (this Hero) Show() {
fmt.Println("Name = ", this.Name)
fmt.Println("Ad = ", this.Ad)
fmt.Println("Level = ", this.Level)
}

func (this Hero) GetName() string {
return this.Name
}

func (this Hero) SetName(newName string) {
this.Name = newName
} */

func (this *Hero) Show() {
fmt.Println("Name = ", this.Name)
fmt.Println("Ad = ", this.Ad)
fmt.Println("Level = ", this.Level)
}

func (this *Hero) GetName() string {
return this.Name
}

func (this *Hero) SetName(newName string) {
this.Name = newName
}

func main() {
// 创建一个对象
hero := Hero{Name: "zhangsan", Ad: 100, Level: 10}
hero.Show()

hero.SetName("LiSi")
hero.Show()
}

输出

1
2
3
4
5
6
Name =  zhangsan
Ad = 100
Level = 10
Name = LiSi
Ad = 100
Level = 10

继承

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
package main

import "fmt"

type Human struct {
name string
sex string
}

func (this *Human) Eat() {
fmt.Println("Human.Eat()...")
}

func (this *Human) Walk() {
fmt.Println("Human.Walk()...")
}

// ===========================================

type SuperMan struct {
Human // SuperMan类继承了Human类的方法
Level int
}

// 重写父类的方法Eat()
func (this *SuperMan) Eat() {
fmt.Println("SuperMan.Eat()...")
}

// 子类的新方法
func (this *SuperMan) Fly() {
fmt.Println("SuperMan.Fly()...")
}

func (this *SuperMan) Print() {
fmt.Println("name = ", this.name)
fmt.Println("sex = ", this.sex)
fmt.Println("Level = ", this.Level)
}

func main() {
h := Human{"zhangsan", "female"}
h.Eat()
h.Walk()

fmt.Println("=============================")
// 定义一个子类对象
// s := SuperMan{Human{"lisi", "female"}, 10}
// 等价
var s SuperMan
s.name = "lisi"
s.sex = "female"
s.Level = 10

s.Walk() // 父类的方法
s.Eat() // 子类的方法
s.Fly() // 子类的方法

s.Print()
}

输出

1
2
3
4
5
6
7
8
9
Human.Eat()...
Human.Walk()...
=============================
Human.Walk()...
SuperMan.Eat()...
SuperMan.Fly()...
name = lisi
sex = female
Level = 10

多态

基本要素:

  1. 有一个父类(有接口)

20230524150652

  1. 有子类(实现了父类的全部接口方法)

20230524150819

  1. 父类类型的变量(指针)指向(引用)子类的具体数据变量

20230524150936

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
package main

import "fmt"

// 本质是一个指针
type AnimalIF interface {
Sleep()
GetColor() string // 获取动物的颜色
GetType() string // 获取动物的种类
}

// 具体的类
type Cat struct {
color string // 猫的颜色
}

func (this *Cat) Sleep() {
fmt.Println("Cat Sleep...")
}

func (this *Cat) GetColor() string {
return this.color
}

func (this *Cat) GetType() string {
return "Cat"
}

// 具体的类
type Dog struct {
color string // 猫的颜色
}

func (this *Dog) Sleep() {
fmt.Println("Dog Sleep...")
}

func (this *Dog) GetColor() string {
return this.color
}

func (this *Dog) GetType() string {
return "Dog"
}

func showAnimal(animal AnimalIF) {
animal.Sleep() // 多态
fmt.Println("color = ", animal.GetColor())
fmt.Println("type = ", animal.GetType())
}

func main() {
var animal AnimalIF // 接口的数据类型,父类指针
animal = &Cat{"White"}
animal.Sleep() // 调用的就是Cat的Sleep方法

animal = &Dog{"Yellow"}
animal.Sleep() // 调用的就是Dog的Sleep方法

fmt.Println("===============================")

cat := Cat{"White"}
dog := Dog{"Yellow"}

showAnimal(&cat)
showAnimal(&dog)
}

输出

1
2
3
4
5
6
7
8
9
Cat Sleep...
Dog Sleep...
===============================
Cat Sleep...
color = White
type = Cat
Dog Sleep...
color = Yello
type = Dog

interface

Golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(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
package main

import "fmt"

// interface{}是万能数据类型
func myfunc(arg interface{}) {
fmt.Println("myfunc is called...")
fmt.Println(arg)

// interface{}该如何区分此时引用的底层数据类型到底是什么呢

// interface{}有"类型断言"机制
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
fmt.Println("arg is string type, value = ", value)
fmt.Printf("value type is %T\n", value)
}
}

type Book struct {
auth string
}

func main() {
book := Book{"Golang"}
myfunc(book)
myfunc(100)
myfunc("abc")
myfunc(3.14)
}

反射reflect

在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。

多插一句,Golang的gRPC也是通过反射实现的。

interface和反射

在讲反射之前,先来看看Golang关于类型设计的一些原则

  • 变量包括(type, value)两部分
  • type 包括 static typeconcrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete typeruntime系统看见的类型
  • 类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.

接下来要讲的反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。

在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:

1
(value, type)

value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。

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

import "fmt"

func main() {
var a string
// pair<statictype:string, value:"zhangsan">
a = "zhangsan"

// pair<type:string, value:"zhangsan">
var allType interface{}
allType = a

str, _ := allType.(string)
fmt.Println(str)
}

输出

1
zhangsan
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
package main

import (
"fmt"
"io"
"os"
)

func main() {
// tty: pair<type:*os.File, value:"/dev/tty"文件描述符>
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
fmt.Println("open file error", err)
return
}

// r: pair<type:, value: >
var r io.Reader
// r: pair<type:*os.File, value:"/dev/tty"文件描述符>
r = tty

// w: pair<type:, value: >
var w io.Writer
// w: pair<type:*os.File, value:"/dev/tty"文件描述符>
w = r.(io.Writer)

w.Write([]byte("HELLO THIS IS A TEST!!!\n"))
}
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
package main

import "fmt"

type Reader interface {
ReadBook()
}

type Writer interface {
WriteBook()
}

// 具体类型
type Book struct {
}

func (this *Book) ReadBook() {
fmt.Println("Read a Book")
}

func (this *Book) WriteBook() {
fmt.Println("Write a Book")
}

func main() {
// b: pair<type: Book, value: book{}地址>
b := &Book{}

// r: pair<type: , value: >
var r Reader
// r: pair<type: Book, value: book{}地址>
r = b
r.ReadBook()

var w Writer

w = r.(Writer) // 此处的断言会成功。因为w和r具体的type是一致的
w.WriteBook()
}

输出

1
2
Read a Book
Write a Book

反射的基本使用

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

import (
"fmt"
"reflect"
)

func reflectNum(arg interface{}) {
fmt.Println("type: ", reflect.TypeOf(arg))
fmt.Println("value: ", reflect.ValueOf(arg))
}

func main() {
var num float64 = 1.2456
reflectNum(num)
}

输出

1
2
type:  float64
value: 1.2456
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
package main

import (
"fmt"
"reflect"
)

type User struct {
Id int
Name string
Age int
}

func (this User) Call() {
fmt.Println("user is called...")
fmt.Printf("%v\n", this)
}

func main() {
user := User{1, "lpc", 18}
DoFiledAndMethod(user)
}

func DoFiledAndMethod(input interface{}) {
// 获取input的type
inputType := reflect.TypeOf(input)
fmt.Println("inputType is :", inputType.Name())
// 获取input的value
inputValue := reflect.ValueOf(input)
fmt.Println("inputValue is :", inputValue)

// 通过type获取里面的字段
// 1.获取interface的reflect.Type,通过Type得到NumField,进行遍历
// 2.得到每个field,数据类型
// 3.通过field有一个Interface()方法得到对应的value
for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i)
value := inputValue.Field(i).Interface()

fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}

// 通过type获取里面的方法
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}

输出

1
2
3
4
5
6
inputType is : User
inputValue is : {1 lpc 18}
Id: int = 1
Name: string = lpc
Age: int = 18
Call: func(main.User)

结构体标签

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 main

import (
"fmt"
"reflect"
)

type resume struct {
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex"`
}

func findTag(stru interface{}) {
t := reflect.TypeOf(stru).Elem()

for i := 0; i < t.NumField(); i++ {
tagInfo := t.Field(i).Tag.Get("info")
tagDoc := t.Field(i).Tag.Get("doc")
fmt.Println("info: ", tagInfo, "doc: ", tagDoc)
}
}

func main() {
var stru resume
findTag(&stru)
}

输出

1
2
info:  name doc:  我的名字
info: sex doc:

应用

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
package main

import (
"encoding/json"
"fmt"
)

type Movie struct {
Title string `json: "title"`
Year int `json: "year"`
Price int `json:"rmb"`
Actors []string `json:"actors"`
}

func main() {
movie := Movie{"喜剧之王", 2000, 10, []string{"xingye", "zhangguozhi"}}

// 编码的过程 结构体--->json
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error", err)
return
}
fmt.Println("结构体--->json")
fmt.Printf("jsonStr = %s\n", jsonStr)

// 解码的过程 json--->结构体
// jsonStr = {"Title":"喜剧之王","Year":2000,"rmb":10,"actors":["xingye","zhangguozhi"]}
myMovie := Movie{}
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("json unmarshal error", err)
return
}
fmt.Println("json--->结构体")
fmt.Printf("%v\n", myMovie)
}

输出

1
2
3
4
结构体--->json
jsonStr = {"Title":"喜剧之王","Year":2000,"rmb":10,"actors":["xingye","zhangguozhi"]}
json--->结构体
{喜剧之王 2000 10 [xingye zhangguozhi]}

Golang高阶

Goroutine

协程并发

协程:co-routine。也叫轻量级线程。

与传统的系统级线程和进程相比,协程最大的优势在于“轻量级”。可以轻松创建上万个而不会导致系统资源衰竭。而线程和进程通常很难超过1万个。这也是协程别称“轻量级线程”的原因。

一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。

多数语言在语法层面并不直接支持协程,而是通过库的方式支持,但用库的方式支持的功能也并不完整,比如仅仅提供协程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行轻量级线程,从而无法真正达到轻量级线程本身期望达到的目标。

在协程中,调用一个任务就像调用一个函数一样,消耗的系统资源最少!但能达到进程、线程并发相同的效果。

在一次并发任务中,进程、线程、协程均可以实现。从系统资源消耗的角度出发来看,进程相当多,线程次之,协程最少。

Go并发

Go 在语言级别支持协程,叫goroutine。Go 语言标准库提供的所有系统调用操作(包括所有同步IO操作),都会出让CPU给其他goroutine。这让轻量级线程的切换管理不依赖于系统的线程和进程,也不需要依赖于CPU的核心数量。

有人把Go比作21世纪的C语言。第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持并发。同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制。

Go语言为并发编程而内置的上层API基于顺序通信进程模型CSP(communicating sequential processes)。这就意味着显式锁都是可以避免的,因为Go通过相对安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写。

Go语言中的并发程序主要使用两种手段来实现。goroutinechannel

什么是Goroutine

goroutine是Go语言并行设计的核心,有人称之为go程。 Goroutine从量级上看很像协程,它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

一般情况下,一个普通计算机跑几十个线程就有点负载过大了,但是同样的机器却可以轻松地让成百上千个goroutine进行资源竞争。

创建Goroutine

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。

在并发编程中,我们通常想将一个过程切分成几块,然后让每个goroutine各自负责一块工作,当一个程序启动时,主函数在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用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
package main

import (
"fmt"
"time"
)

func newTask() {
i := 0
for {
i++
fmt.Printf("new Goroutine: i = %d\n", i)
time.Sleep(1 * time.Second)
}
}

func main() {
// 创建一个go协程去执行newTask
go newTask()

// main goroutine 循环打印
i := 0
for {
i++
fmt.Printf("main Goroutine: i = %d\n", i)
time.Sleep(1 * time.Second)
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
main Goroutine: i = 1
new Goroutine: i = 1
new Goroutine: i = 2
main Goroutine: i = 2
main Goroutine: i = 3
new Goroutine: i = 3
new Goroutine: i = 4
main Goroutine: i = 4
main Goroutine: i = 5
new Goroutine: i = 5
new Goroutine: i = 6
main Goroutine: i = 6
main Goroutine: i = 7
new Goroutine: i = 7
new Goroutine: i = 8
main Goroutine: i = 8
main Goroutine: i = 9
new Goroutine: i = 9
new Goroutine: i = 10
main Goroutine: i = 10
...

Goroutine特性

主goroutine退出后,其它的工作goroutine也会自动退出

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

import (
"fmt"
"time"
)

func newTask() {
i := 0
for {
i++
fmt.Printf("new Goroutine: i = %d\n", i)
time.Sleep(1 * time.Second)
}
}

func main() {
// 创建一个go协程去执行newTask
go newTask()

fmt.Println("main goroutine exit")
}

输出

1
main goroutine exit

Goexit函数

调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。

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

import (
"fmt"
"runtime"
)

func main() {
go func() {
defer fmt.Println("A.defer")

func() {
defer fmt.Println("B.defer")
runtime.Goexit() // 终止当前 goroutine, import "runtime"
fmt.Println("B") // 不会执行
}()

fmt.Println("A") // 不会执行
}() //不要忘记()

//死循环,目的不让主goroutine结束
for {
}
}

输出

1
2
3
B.defer
A.defer
exit status 0xc000013a

Channel

channel是Go语言中的一个核心类型,可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。

channel是一个数据类型,主要用来解决go程的同步问题以及go程之间数据共享(数据传递)的问题。

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。

引⽤类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。

channel

定义channel变量

和map类似,channel也一个对应make创建的底层数据结构的引用

当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil

定义一个channel时,也需要定义发送到channel的值的类型。channel可以使用内置的make()函数来创建:

chan是创建channel所需使用的关键字。Type代表指定channel收发数据的类型。

1
2
make(chan Type)  //等价于make(chan Type, 0)
make(chan Type, capacity)

当 参数capacity= 0 时,channel 是无缓冲阻塞读写的;当capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入。

channel非常像生活中的管道,一边可以存放东西,另一边可以取出东西。channel通过操作符 <- 来接收和发送数据,发送和接收数据语法:

1
2
3
4
channel <- value      //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //功能同上,同时检查通道是否已关闭或者是否为空

默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine同步变的更加的简单,而不需要显式的lock

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

import (
"fmt"
)

func main() {
c := make(chan int)

go func() {
defer fmt.Println("goroutine结束")

fmt.Println("goroutine正在运行……")

c <- 666 // 将666发送到c
}()

num := <-c // 从c中接收数据,并赋值给num

fmt.Println("num = ", num)
fmt.Println("main goroutine结束")
}

输出

1
2
3
4
goroutine正在运行……
goroutine结束
num = 666
main goroutine结束