go 语言速成

基本语法

HelloWorld简单示例

1
2
3
4
package main
func main(){
fmt.Println("Hello World!")
}

终端运行

1
2
$ go run helloworld.go
Hello World

go run表示直接运行 直接编译 go 语言并执行应用程序,一步完成 也可以先 go build helloworld.go || ./helloworld.go build成静态语言然后在执行

go 语言的语言, 写出规范的一致代码是很重要的 比如写函数的时候’{‘ 必须和函数名在同一行 建议使用 go 的默认格式化程序来约束

上面的执行流程是

  • package main: 定义了包名 必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包
  • import “fmt”: 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
  • func main(): main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)
  • fmt.Println(…): 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。

变量声明

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

var x, y int
var (
a int = 1
b bool = true
)
var c, d int = 1,2
var e, f = 123, "yueyun"
// 这种不带声明格式的只能在函数体内声明
// g, h := 123, "需要在func函数体内实现"
func main() {
g,h := 123, "explosion"
// 使用 _ 接受不要的参数
_,t = nil, 'Explosion'
fmt.Println(g,h.t)
}

声明⼀个变量 默认的值是 0 var a int
声明⼀个变量,初始化⼀个值 var b int = 100
在初始化的时候,可以省去数据类型,通过值⾃动匹配当前的变量的数据类型 var c = 100
(常⽤的⽅法) 省去 var 关键字,直接⾃动匹配 e := 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
25
26
27
28
29
30
31
package main

import "fmt"

func main() {
const a int = 10
const (
b int = 20
c string = "Hello"
)
// iota与const来表示枚举类型
const (
BEJing = 10 * iota //iota=0
SHANGHAI //iota=1
SHENZHEN //iota=2
)
// 按照行来给 iota 来赋值
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota) // 1 << (10*1)
MB // 1 << (10*2)
GB // 1 << (10*3)
TB // 1 << (10*4)
PB // 1 << (10*5)
EB // 1 << (10*6)
ZB // 1 << (10*7)
YB // 1 << (10*8)
)
fmt.Println(a, b, c, BEJing, SHANGHAI, SHENZHEN)
// ..
}

函数

基本示例

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 foo1() {
fmt.Println("foo1")
}

// 返回多个返回值 匿名
func foo2(a int, b string) (int, string) {
fmt.Println("foo2", a, b)
return a + 1, b + "explosion"
}
func foo3(a int, b string) (c int, d string) {
// c d 属于 foo3的形参 初始的默认值是0 作用域空间是foo3 函数体{}内
fmt.Println("foo3", a, b, c, d)
c = a + 1
d = b + "explosion"
return
}
func main() {
foo1()
foo2(1, "hello")
foo3(2, "world")
}

函数传参

函数传递参数包括值传递和引用传递

这里首先介绍一下指针

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

以下定义了 swap() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
/* 定义相互交换值的函数 */
func swap(x, y int) int {
var temp int


temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/


return temp;
}

接下来,让我们使用值传递来调用 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main


import "fmt"


func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200


fmt.Printf("交换前 a 的值为 : %d\n", a )
fmt.Printf("交换前 b 的值为 : %d\n", b )


/* 通过调用函数来交换值 */
swap(a, b)


fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}


/* 定义相互交换值的函数 */
func swap(x, y int) int {
var temp int


temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/


return temp;
}

/*以下代码执行结果为:
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
*/

引用传递(指针传递)

引用传递指针参数传递到函数内,以下是交换函数 swap() 使用了引用传递:

1
2
3
4
5
6
7
/* 定义交换值函数*/
func swap(x *int, y *int) {
var temp int
temp = *x /* 保持 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
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"
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前,a 的值 : %d\n", a )
fmt.Printf("交换前,b 的值 : %d\n", b )

/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
swap(&a, &b)
fmt.Printf("交换后,a 的值 : %d\n", a )
fmt.Printf("交换后,b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}

/*
以上代码执行结果为:
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100
*/

init 函数与导包

现在在一个 go 的工程目录下有下面的文件

1
2
3
4
5
6
7
8
9
tree /F /A
E:.
| main.go
|
+---lib1
| lib1.go
|
|---lib2
lib2.go

main.go

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

import (
"fmt"
"/go/lib1"
"/go/lib2"
)

func main() {
fmt.Println("main()....")
lib1.Lib1Test()
lib2.Lib2Test()
}

lib1.go

1
2
3
4
5
6
7
8
9
10
11
12
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
12
13
package lib2

import "fmt"

// 导出的包必须大写才可见
func Lib2Test() {
fmt.Println("Lib2Test()....")
}

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

import 导包的时候

  • import _ “fmt”: 给 fmt 包起⼀个别名,匿名, ⽆法使⽤当前包的⽅法,但是 会执⾏当前的包内部的 init()⽅法
  • import aa “fmt” 给 fmt 包起⼀个别名,aa, aa.Println()来直接调⽤
  • import . “fmt” 将当前 fmt 包中的全部⽅法,导⼊到当前本包的作⽤中,fmt 包中 的全部的⽅法可以直接使⽤ API 来调⽤,不需要 fmt.API 来调⽤

defer

defer 的执行顺序

执行顺序是 fun3() –> fun2() –> fun1()

defter 和 return

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

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

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

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

func main() {
returnAndDefer()
}

/**
return func called...
defer func called...
*/

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

切片和 slice

数组

数组长度是固定的 固定长度的数据在传参的时候是严格匹配数组类型的

1
2
3
4
5
6
7
8
9
10
package main
func main(){
var myArray1 = [10]int // 默认值是0
myArray2 := [10]int{1,2,3,4}
myArray3 := [4]int{11,22,33,44}
}

func printArray(myArray [4]int) {
// 值拷贝 而且形参类型固定
}

动态数组 slice

动态数组的参数上是引用传递 而且不同元素长度的动态数组他们的形参是一致

声明方式

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

import (
"fmt"
)

func main() {
myArray := []int{1, 2, 3, 4} // 动态数组 切片 slice
// 声明slice
slice := []int{1, 2, 3}
var slice1 []int // nil slice 声明是一个切片但是没有给slice分配空间
slice1 = make([]int, 3) // 开辟3个空间 默认值是0
var slice2 = make([]int, 3, 5) // 开辟3个空间 默认值是0 5是底层数组的长度 cap
slice3 := make([]int, 3) // 常用 隐式 推导
fmt.Println(slice, slice1, slice2, slice3)
fmt.Println(myArray)
}

func printArray(myArray []int) {
for _, v := range myArray {
fmt.Println(v)
}
}

使用方式

切片的容量和追加:切片的长度和容量不同,长度表示左指针至右指针的距离,容量表示左指针至底层数组末尾的距离

切⽚的扩容机制,append的时候,如果⻓度增加后超过容量,则将容量增加2倍

切片截取

可以通过设置下限及上限来设置截取切片*[lower-bound:upper-bound]*,实例如下

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 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]

append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

输出结果

1
2
3
4
5
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]

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
43
44
45
46
47
48
49
50
51
52
package main
import (
"fmt"
)

func main() {
//第一种声明
var test1 map[string]string
//在使用map前,需要先make,make的作用就是给map分配数据空间
test1 = make(map[string]string, 10)
test1["one"] = "php"
test1["two"] = "golang"
test1["three"] = "java"
fmt.Println(test1) //map[two:golang three:java one:php]
//第二种声明
test2 := make(map[string]string)
test2["one"] = "php"
test2["two"] = "golang"
test2["three"] = "java"
fmt.Println(test2) //map[one:php two:golang three:java]

//第三种声明
test3 := map[string]string{
"one" : "php",
"two" : "golang",
"three" : "java",
}
fmt.Println(test3) //map[one:php two:golang three:java]
language := make(map[string]map[string]string)
language["php"] = make(map[string]string, 2)
language["php"]["id"] = "1"
language["php"]["desc"] = "php是世界上最美的语言"
language["golang"] = make(map[string]string, 2)
language["golang"]["id"] = "2"
language["golang"]["desc"] = "golang抗并发非常good"

fmt.Println(language) //map[php:map[id:1 desc:php是世界上最美的语言] golang:map[id:2 desc:golang抗并发非常good]]


//增删改查
// val, key := language["php"] //查找是否有php这个子元素
// if key {
// fmt.Printf("%v", val)
// } else {
// fmt.Printf("no");
// }

//language["php"]["id"] = "3" //修改了php子元素的id值
//language["php"]["nickname"] = "啪啪啪" //增加php元素里的nickname值
//delete(language, "php") //删除了php子元素
fmt.Println(language)
}

面向对象特征

封装

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

import "fmt"

type Human struct {
name string
sex string
}

func (this *Human) Eat() {
fmt.Println(this.name, "Humen.Eating()...")
}

func (this *Human) Walk() {
fmt.Println(this.name, "Humen.Walking()...")
}

func (this *Human) setName(name string) {
this.name = name
}

func main() {
humanTest := Human{name: "月晕", sex: "男"}
humanTest.Eat()
humanTest.Walk()
humanTest.setName("月晕2")
humanTest.Eat()
humanTest.Walk()
}

(this Human) 代表的只是值拷贝 而(this *Human)是传递的*Human 类型 go 会取地址传进去 即可以修改到原属性的值

类名、属性名、方法名 首字母大写表示对外(其他包)可以访问,否则只能够在本包内访问

继承

父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Human struct {
name string
sex string
}

func (this *Human) Eat() {
fmt.Println(this.name, "Humen.Eating()...")
}

func (this *Human) Walk() {
fmt.Println(this.name, "Humen.Walking()...")
}

func (this *Human) setName(name string) {
this.name = name
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type SuperMan struct {
Human
level int
}

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

// 子类的新方法
func (this *SuperMan) Print() {
fmt.Println("name = ", this.name)
// ...
}

定义子类

1
2
3
4
5
// s := SuperMan{Human{"yueyun","female"},98}
var s SuperMan
s.name = "yueyun"
s.sex = "♂"
s.level = 98

多态

基本要素

有一个父类(有接口)

1
2
3
4
5
6
// 本质意义上是一个指针
type AnimalIF interface{
Sleep()
GetColor() string // 获取动物的颜色
GetType() string // 获取动物的种类
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
typo Cat struct {
color String // 猫的颜色
}

func (this *Cat) Sleep() {
fmt.Println("Cat is Sleep")
}
func (this *Cat) GetColor() string {
return this.color
}
func (this *Cat) GetType() string {
return "Cat"
}

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

1
2
3
4
5
6
7
8
var animal AnimalIF // 接口类型 分类指针
animal = &Cat{"Green"}
animal.Sleep() // 调用的就是Cat的Sleep()方法 多态现象

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

interface

通用万能类型 interface{} – 空接口

int string float等 都是先了interface{} 就可以用interface类型引用任意的数据类型

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 myFunc(arg interface{}) {
// do something
fmt.Println("myFunc is called...")
fmt.Println(arg)
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
// ...
}
}

func main() {
var i int = 5
var s string = "Hello world"
myFunc(i)
myFunc(s)
}

类型断言

1
2
3
4
5
6
7
8
9
func myFunc(arg interface{}) {
// do something
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
// ...
}
}

反射

pair

pair是一个对 不管怎么传递都不会变化

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

import "fmt"

func main() {
var a string
// pair <statictype: string, value: "yueyun">
a = "yueyun"
var allType interface{}
// pair 存在一起且传递
// pair <type: interface, value: "yueyun">
allType = a
str, _ := allType.(string)
fmt.Println(str)
}

pair在传递的过程中保持不变

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

import "fmt"

type Writer interface {
WriteBook()
}

type Reader interface {
ReadBook()
}

type Book struct {
}

func (this *Book) WriteBook() {
fmt.Println("write book")
}

func (this *Book) ReadBook() {
fmt.Println("read 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: pair <type: *Book, value: Book{}地址>
w = r.(Writer) // 此处的断言成功 因为 w 和 r的pair是一致的 type都是Book 而Book实现了Writer接口
w.WriteBook()
}

pair 是一直存在而且不会变化

reflect包

包括基本函数 Valueof || Typeof

1
2
3
4
func Valueof (i interface{}) Value {...}
// 用来获取输入参数接口中的数据值 如果接口为空则返回0
func Typeof (i interface{}) Type {...}
// Typeof 用来动态获取输入参数接口中的值的类型 如果接口为空则返回nil

reflect基本用法

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.114514
reflectNum(num)
}

复杂类型

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
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, "yueyun", 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. 通过reflect.Type的Field获取其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)
}
}

结构体标签

json 解编码 orm映射关系

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" docs:"我的名字"`
Sex string `info:"sex"`
}

func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
taginfo := t.Field(i).Tag.Get("info")
tagdoc := t.Field(i).Tag.Get("docs")
fmt.Println("info:", taginfo, "docs:", tagdoc)
}
}

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

json转换

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 (
"encoding/json"
"fmt"
)

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

func main() {
movie := Moive{"喜剧之王", 2000, 10, []string{"周星驰", "张柏芝"}}
// 编码
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error:", err)
return
}
fmt.Printf("%s\n", jsonStr)
// 解码
myMoive := Moive{}
err = json.Unmarshal(jsonStr, &myMoive)
if err != nil {
fmt.Println("json unmarshal error:", err)
return
}
fmt.Printf("%v\n", myMoive)
}

高级语法

goroutine基本模型和调度设计策略

早期的单线程操作系统

存在的问题

  • 单一执行流程, 计算机只能一个一个任务的去处理
  • 进程阻塞所带来的CPU浪费时间(密集IO处理 )

多线程/多进程操作系统

多进程/多线程 解决了阻塞的问题

但是当cpu工作在切换线程的时候会存在很高的切换成本 导致CPU利用率很低, 进程/线程数量越大 切换的成本就越大 也就越浪费 多线程 随着同步竞争(如 锁 竞争资源冲突等) 开发设计会变得越来越复杂 且内存占用多

协程

co-routine 的设计

Goland 对协程的处理: 内存很小可以大量 灵活调度 可以经常切换

Goland对早期调度器的处理

创建 销毁 调度G都需要每个M获取锁 形成了激烈的锁竞争
M转移G的时候会造成延迟和额外的系统负载
系统调用(CPU与M之间的切换)导致频繁的线程阻塞和取消阻塞增加了系统开销

GMP

go现在的设计

调度器的设计策略

复用线程

  • wrok stealing 机制
  • hand off 机制

利用并行

GOMAXPROCS限定P的个数 = cpu核数 / 2

抢占

平均特点 抢占式

全局G队列

goroutine

goroutine普通函数写法

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

// 从主 goroutine 启动一个新的 goroutine
func newTask() {
i := 0
for {
i++
fmt.Println("new Goroutine: i = ", i)
time.Sleep(1 * time.Second)
}
}

// 主 goroutine
func main() {
// 创建一个 goroutine 去执行 newTask()流程
go newTask()
i := 0
for {
i++
fmt.Println("main Goroutine: i = ", i)
time.Sleep(1 * time.Second)
}
}

goroutine匿名函数写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
// 终止退出
runtime.Goexit()
fmt.Println("B")
}()
fmt.Println("A")
}()
for {
time.Sleep(time.Second * 1)
}
}
1
2
3
4
5
6
7
8
9
10
func main() {
go func(a int, b int) bool {
defer fmt.Println("A.defer")
fmt.Println("a=", a, "b=", b)
return false
}(10, 20)
for {
time.Sleep(time.Second * 1)
}
}

runtime.Goexit() 退出当前的goroutine

channel 管道

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
// 定义一个channel
ch := make(chan string)
// 启动一个并发匿名函数
go func() {
// 从channel中取出数据并打印
defer println("func goroutine exit.")
println("func goroutine starts sending...")
ch <- "EXPLOSION!!!" // 往channel中发送数据
}()
// 从channel中取出数据并打印
println("main goroutine starts receiving...")
str := <-ch // 从channel中接收数据
println("main goroutine received:", str)
defer println("main goroutine exit.")
}

基本分析图示

chan 管道具有连接两个协程并且具有同步阻塞的功能

具有缓冲的channel

无缓冲的channel

  • 两个goroutine都达到通道
  • 左侧goroutine开始发送数据到通道中 这时右侧的goroutine会在通道中被锁住 直到交换完成
  • 右侧的goroutine开始接受数据 左侧的goroutine也将会被锁住
  • 交换数据完成 这时两个goroutine会被解锁 可以继续跑下面的流程

有缓冲的channel

  • 左侧的goroutine 传输数据 直到管道写满或者自己停止
  • 右侧的goroutine获取数据 左侧的goroutine新发送数据
  • 左侧发送新增 右边接受 这两种操作既不是同步的也不会相互阻塞
  • 所有的发送和接受完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
c := make(chan int, 3) // 带有缓冲的channel
fmt.Println("len(c)=", len(c), "cap(c)=", cap(c))
go func() {
defer fmt.Println("子go程结束")
for i := 0; i < 5; i++ {
c <- i
fmt.Println("子go程正在运行,发送的元素=", i, "len(c)=", len(c), "cap(c)=", cap(c))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 5; i++ {
// 从channel取出数据
num := <-c
fmt.Println("num=", num)
}
fmt.Println("main go程结束")
}
  • 当channel已经满 在向里面写数据就会阻塞
  • 当channel为空 从里面取数据也会阻塞

channel的关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c) // 关闭channel
}()
for {
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("main go程结束")
}
  • channel 不像文件一样要经常的打开和关闭 只有当确定了没有任何发送的数据了或者想结束range循环才会选择去关闭channel
  • 关闭channel后 无法在向channel 再发送数据(引发panic)
  • 关闭channel后 可以继续从channel接受数据
  • 关于nil channel 无论收发都会去阻塞

channel和range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c) // 关闭channel
}()
// 可以使用range 来迭代不断的从channel接收数据,直到channel被关闭
for data := range c {
fmt.Println(data)
}
fmt.Println("main go程结束")
}

channel和select

单个流程下一个go只能监听一个channel的状态 select可以完成监控多个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
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
// main go
fibonacci(c, quit)
}

func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
// select 用于监听 channel 上的数据流动
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}

goland context 实现原理

context是goland中的经典工具,主要在异步场景中用于实现并发协调以及对 goroutine 的生命周期控制. 除此之外,context 还兼有一定的数据存储能力.

核心数据结构

context.Context

Go modules

go modules 模式

使用go modgo modules 进行项目依赖管理

Go PATH的工作模式弊端: 无版本控制的概念, 无法同步一致第三方版本号 无法指定当前

go mod 的一些命令

  • go mod init 生成go.mod 文件
  • go mod download 下载go.mod文件中指明的所有依赖
  • go mod tidy 整理现有的依赖
  • go mod vendor 导出项目所有的依赖到vendor目录
  • … 等等等

go mod 的环境变量

  • GO111MODULE: auto||on||off 设置 go env -w GO111MODULE=on
  • GOPROXY: 默认值 国内代理: “https://mirrors.aliyun.com/goproxy/$ go env -w GOPROXY=https://goproxy.cn,directdirect 用于指示Go回源到模块版本的源地址去抓取
  • GOSUMDB: 用于在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止
  • GONOPROXY/GONOSUMDB/GOPRIVATE: 建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote" 不经过 GOPROXY, GOSUMDB

go env 来查看环境变量

go modules 初始化项目

  1. 开启Go Modules模块
    保证GOMODULE=on

  2. 初始化项目
    任意文件夹下面创建一个项目

    1
    $ mkdir -p ~/project/module_test

    创建go mod 文件同时起当前项目名

    1
    $ go init github.com/yueyun/module_test

    生成go.mod文件 内容为

    1
    2
    module github.com/yueyun/module_test
    go 1.21.6

    在该项目编写代码, 如果源代码依赖某个库 如(github.com/apprehen/zinx/znet)可以
    手动down: go get github.com/apprehen/zinx/znet
    自动donw

    go mod 文件下面会新增加一行代码

    1
    require github.com/apprehen/zinx v0.0.0-20200315083925-f09df55dc746,indirect

    含义当前模块依赖github.com/apprehen/zinx 依赖版本是后面的

    //indirct 表示间接依赖 因为项目直接依赖的是znet包 间接依赖的是zinx包

  3. 生成一个go.sum 文件
    go.sum文件的作用 罗列当前项目直接引用或间接的依赖所有模块版本 保证今后项目依赖的版本不会被篡改
    h1:hash: 表示整体项目的zip文件打开之后的全部文件的校验和来生成的hash 如果不存在 则表示依赖的库可能用不上
    xxx/go.mod h1:hash go.mod文件做的hash

Go语言项目案例 IM-System

构建基础的server

main.go

1
2
3
4
5
6
package main

func main () {
server := NewServer("127.0.0.1", 8800)
server.Start()
}

server.go

server类型

1
2
3
4
type Server struct {
IP string
Port int
}

server方法

创建一个server对象

1
2
3
4
5
6
7
func NewServer (IP string,Port int) *Server {
server := &Server{
IP: IP,
Port: port
}
return server
}

启动 server服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (this *Server) Start () {
//sock listen
listener,err := net.listen("tcp",fnt.Sprintf("%s:%d",this.IP,this.Port))
if err = !nil {
fmt.PrintLn("net.Listen err:",err)
return
}
// close listen
defer listener.Close()
for {
// accept
conn,err = listener.Accept()
if err!= nil {
fmt.Println("listener.Accept err:",err)
continue
}
// do hander
go this.Handler(conn)
}
}

处理连接业务

1
2
3
func (this *Server) Handler(conn net.Conn) {
fmt.Println("建立连接成功")
}

用户上线功能

user.go

user类型

1
2
3
4
5
6
type User struct {
Name string
Addr string
C chan string
conn net.Conn
}

方法

创建一个user对象

1
2
3
4
5
6
7
8
9
10
11
12
func NewUser (conn net.Conn) *User {
userAddr := conn.RemoteAddr().String
user := &User {
Name: userAddr
Addr: userAddr
C: make(chan string)
conn: conn
}
// 启动监听当前user channel消息的goroutine
go user.ListenMessage()
return user
}

监听消息user对应的channel消息

1
2
3
4
5
6
func (this *User) ListenMessage() {
for {
msg := <- this.C
this.conn.Write([]byte(msg + "\n"))
}
}

server.go

server类型

新增OnlineMap和Message属性

1
2
3
4
5
6
7
8
9
type Server struct {
IP string
Port int
// 在线用户的列表
OnlineMap map[string]*User
mapLock sync.RWMutex
// 消息广播的channel
Message chan string
}

在处理客户端上线的Handler创建并添加用户

1
2
3
4
5
6
7
8
9
10
11
12
func (this *Server) Handler(conn net.Conn) {
fmt.Println("建立连接成功")
// 用户上线 将用户加入到onlineMap中
user := NewUser(conn)
this.mapLock.Lock()
this.OnlineMap[user.Name] = user
// 广播当前用户上线消息
this.BroadCast(user, "已上线")
this.mapLock.Unlock()
// 当前handler阻塞
select {}
}

新增广播消息方法

1
2
3
4
func (this *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
this.Message <- sendMsg
}

新增监听广播消息channel方法

1
2
3
4
5
6
7
8
9
10
11
func (this *Server) ListenMessager() {
for {
msg := <-this.Message
// 将msg发送给全部的在线User
this.mapLock.Lock()
for _, cli := range this.OnlineMap {
cli.C <- msg
}
this.mapLock.Unlock()
}
}

用一个goroutine单独监听Message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (this *Server) Start() {
// socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.IP, this.Port))
if err != nil {
fmt.Println("net.Listen err:", err)
return
}
// close listen socket
defer listener.Close()
// 启动监听Message的goroutine
go this.ListenMessager() // 111
for {
// accept 连接读写的套接字
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err:", err)
continue
}
// do handler
go this.Handler(conn)
}
}

用户消息广播机制

server.go

完善handle处理业务的方法 启动一个针对当前客户端的读goroutine

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
func (this *Server) Handler(conn net.Conn) {
// 用户上线 将用户加入到onlineMap中
user := NewUser(conn)
this.mapLock.Lock()
this.OnlineMap[user.Name] = user
// 广播当前用户上线消息
this.BroadCast(user, "已上线")
this.mapLock.Unlock()
// 接受客户端发送的消息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 { // 对端断开或者出问题
this.BroadCast(user, "下线")
return
}
if err != nil && err != io.EOF {
fmt.Println("conn.Read err:", err)
return
}
// 提取用户的消息
msg := string(buf[:n-1])
// 将得到的消息进行广播
this.BroadCast(user, msg)
}
}()
// 当前handler阻塞
select {}
}

用户业务层封装

user.go

user类型新增server关联

1
2
3
4
5
6
7
type User struct {
Name string
Addr string
C chan string
conn net.Conn
server *Server
}

新增Online方法

1
2
3
4
5
6
7
func (this *User) Online() {
this.server.mapLock.Lock()
this.server.OnlineMap[this.Name] = this
// 广播当前用户上线消息
this.server.BroadCast(this, "已上线")
this.server.mapLock.Unlock()
}

新增Offline方法

1
2
3
4
5
6
7
func (this *User) Offline() {
this.server.mapLock.Lock()
delete(this.server.OnlineMap, this.Name)
// 广播当前用户下线消息
this.server.mapLock.Unlock()
this.server.BroadCast(this, "下线")
}

新增DoMessage方法

1
2
3
func (this *User) DoMessage(msg string) {
this.server.BroadCast(this, msg)
}

server.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 (this *Server) Handler(conn net.Conn) {
// 用户上线 将用户加入到onlineMap中
user := NewUser(conn, this)
user.Online()
// 接受客户端发送的消息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 { // 对端断开或者出问题
user.Offline()
return
}
if err != nil && err != io.EOF {
fmt.Println("conn.Read err:", err)
return
}
// 提取用户的消息
msg := string(buf[:n-1])
// 用户针对msg进行消息处理
user.DoMessage(msg)
}
}()
// 当前handler阻塞
select {}
}

在线用户查询

user.go

提供SendMsg向对象客户端发送消息API

1
2
3
func (this *User) SendMsg(msg string) {
this.conn.Write([]byte(msg))
}

DoMessage()方法中 加上对who指令的处理 返回在线用户的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
func (this *User) DoMessage(msg string) {
//
if msg == "who" {
// 查询当前在线用户
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
this.SendMsg(onlineMsg)
}
} else {
this.server.BroadCast(this, msg)
}
}

修改用户名

消息格式为rename 然后修改服务器内部状态改成AwaitUserName在对消息处理设置成新的用户名

修改 DoMessage方法

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 (this *User) DoMessage(msg string) {
switch this.state {
case NormalState:
if msg == "who" {
// 查询当前在线用户
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
this.SendMsg(onlineMsg)
}
this.server.mapLock.unlock()
} else if msg == "rename" {
this.state = AwaitUserNameState
// 用户输入用户名
this.SendMsg("请输入新的用户名: ")
//
} else {
this.server.BroadCast(this, msg)
}
case AwaitUserNameState:
this.Rename(msg)
this.state = NormalState
}
}

新增加 ReName方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (this *User) Rename(newName string) {
if newName == "" {
this.SendMsg("用户名不能为空\n")
return
}
_, ok := this.server.OnlineMap[newName]
if ok {
this.SendMsg("用户名已经被使用\n")
return
} else {
this.SendMsg("用户名正在更改...\n")
this.server.mapLock.Lock()
delete(this.server.OnlineMap, this.Name)
this.server.OnlineMap[newName] = this
this.server.mapLock.Unlock()
this.Name = newName
this.SendMsg("您已经更新用户名:" + this.Name + "\n")
}
}

超时强踢功能

在用户的hander()中 goroutine中 添加用户活跃的channel 一旦有消息 就向该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
func (this *Server) Handler(conn net.Conn) {
// 用户上线 将用户加入到onlineMap中
user := NewUser(conn, this)
user.Online()
isLive := make(chan bool) // ***
// 接受客户端发送的消息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 { // 对端断开或者出问题
user.Offline()
return
}
if err != nil && err != io.EOF {
fmt.Println("conn Read err:", err)
return
}
// 提取用户的消息
msg := string(buf[:n-1])
// 用户针对msg进行消息处理
user.DoMessage(msg)
isLive <- true // ***
}
}()

在用户的hander()中 添加定时器功能 超时则强踢

1
2
3
4
5
6
7
8
9
10
11
12
13
select {
case <-isLive:
// 当前用户活跃 重置定时器
case <-time.After(time.Second * 10):
// 已经超时
user.SendMsg("你被踢了\n")
// 销毁用的资源
close(user.C)
// 关闭连接
conn.Close()
// 退出当前的Handler
return
}

实现私聊功能

消息格式是 to|yueyun|你好啊我是...

user.go

在DoMessage()方法中 加上对to|yueyun|你好啊 指令的处理 返回在线用户的信息

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
if msg == "who" {
// 查询当前在线用户
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
this.SendMsg(onlineMsg)
}
this.server.mapLock.Unlock()
} else if msg == "rename" {
this.state = AwaitUserNameState
// 用户输入用户名
this.SendMsg("请输入新的用户名: ")
} else if len(msg) > 4 && msg[:3] == "to|" {
// 消息格式: to|yueyun|消息内容
// first: get the usernane
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
this.SendMsg("消息格式不正确,请使用\"to|yueyun|消息内容\"格式\n")
return
}
// second: get the user Object
remoteUser, ok := this.server.OnlineMap[remoteName]
if !ok {
this.SendMsg("该用户名不存在\n")
return
}

// third: send the msg
content := strings.Split(msg, "|")[2]
if content == "" {
this.SendMsg("无消息内容,请重发\n")
return
}
remoteUser.SendMsg(this.Name + "对你说:" + content + "\n")
} else {
this.server.BroadCast(this, msg)
}