go

go_demo

go_demo

Posted by DYC on December 22, 2024

go_demo

基础语法

values

Go 有各种值类型,包括字符串、整数、浮点数、布尔值等。这里是一些基本示例

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

import "fmt"

func main() {
	fmt.Println("go" + "lang")
	fmt.Println("1+1 =", 1+1)
	fmt.Println("7.0/3.0 =", 7.0/3.0)
	fmt.Println(true && false)
	fmt.Println(true || false)
	fmt.Println(!true)
}

变量

在 Go 中,变量由编译器明确声明和使用,例如检查函数调用的类型正确性

var可以一次声明一个或多个变量,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"

func main() {
    var a = "initial"
    fmt.Println(a)

    var b, c int = 1, 2
    fmt.Println(b, c)

    var d = true
    fmt.Println(d)

    var e int
    fmt.Println(e)

    f := "apple"
    fmt.Println(f)
}
常量

Go支持字符、字符串、布尔值、数字值的常量

const声明一个常量值,用const取代var即可数字常量没有类型,除非通过显式转换等方式指定类型

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

import (
    "fmt"
    "math"
)

const s string = "constant"

func main() {
    fmt.Println(s)

    const n = 500000000

    const d = 3e20 / n
    fmt.Println(d)

    fmt.Println(int64(d))

    fmt.Println(math.Sin(n))
}
for

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

import "fmt"

func main() {
    i := 1
    for i <= 3 {
       fmt.Println(i)
       i++
    }
    for j := 0; j < 5; j++ {
       fmt.Println(j)
    }
    nums := []int{1, 2, 3}
    for i := range nums {
       fmt.Println("range", i)
    }

    for {
       fmt.Println("loop")
       break
    }
    for i := 1; i <= 6; i++ {
       if i%2 == 1 {
          continue
       }
       fmt.Println(i)
    }
}
If-else

可以有if一个不带 else 的语句,语句可以位于条件之前,此语句声明任何变量可以在该分支及后续分支使用

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() {
	if 7%2 == 0 {
		fmt.Println("7 is even")
	}

	if 8%2 == 0 || 7%2 == 0 {
		fmt.Println("either 8 or 7 are even")
	}
	
	if num := 9; num < 0 {
		fmt.Println(num, "is negative")
	} else if num > 0 {
		fmt.Println(num, "is positive")
	} else {
		fmt.Println(num, "is zero")
	}

}

switch

Switch 语句表达跨多个分支的条件

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

import (
	"fmt"
	"time"
)

func main() {
	i := 2
	fmt.Print("write", i, "as")
	switch i {
	case 1:
		fmt.Println("one")
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	}

	switch time.Now().Weekday() {
  //可以使用逗号分隔同一`case`语句中的多个表达式
	case time.Saturday, time.Sunday:
		fmt.Println("it's the weekend")
	default:
		fmt.Println("it's a weekday")
	}

	t := time.Now()
  //`switch`不使用表达式是表达 if/else 逻辑的另一种方式,
	switch {
	case t.Hour() < 12:
		fmt.Println("it's before noon")
	default:
		fmt.Println("it's after noon")
	}

	whatAmI := func(i interface{}) {
    //类型`switch`比较的是类型而不是值。可以使用它来发现接口值的类型
		switch t := i.(type) {
		case bool:
			fmt.Println("I'm a bool")
		case int:
			fmt.Println("I'm an int")
		case string:
			fmt.Println("I'm a string")
		default:
			fmt.Printf("Don't know type %T\n", t)
		}
	}
	whatAmI(true)
	whatAmI("hey")
	whatAmI(1)
	
}

数组
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() {
    var a [5]int
    fmt.Println(a)

    a[4] = 100
    fmt.Println(a[4])
    fmt.Println(len(a))

    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(b)
		//可以让编译器计算元素的数量...
    b = [...]int{1, 2, 3, 4, 5}
    fmt.Println(b)
    //[100 0 0 400 500]
  	//3: 400表示将3号位置设置为400,由于1号和2号位置没有设置,则默认为0
    b = [...]int{100, 3: 400, 500}
    fmt.Println(b)

    var twoD [2][3]int
    for i := 0; i < len(twoD); i++ {
       for j := 0; j < len(twoD[i]); j++ {
          twoD[i][j] = i + j
       }
    }
    fmt.Println(twoD)
    twoD = [2][3]int{
       {1, 2, 3},
       {2, 3, 4},
    }
    fmt.Println(twoD)

}
切片

切片实际上是一个结构体,结构体内部包含指向底层数组的指针、切片的长度、切片的容量

所以切片实际上是对底层数组的一个封装,自己并不存储数据

所以当使用append方法时,如果len>cap时,就会创建一个新的数组(一般两倍容量)进行迁移,但这个过程并不是并发安全的,所以如果多个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
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
package main

import (
	"fmt"
	"reflect"
	"slices"
)

func main() {
	var s []string
	//[] true true
	fmt.Println(s, s == nil, len(s) == 0)

	s = make([]string, 3)
	//[  ] false 3 3
	fmt.Println(s, s == nil, len(s), cap(s))

	s[0] = "a"
	s[1] = "b"
	s[2] = "c"
	fmt.Println(s)
	fmt.Println(s[2])
	fmt.Println(len(s))
	//需要接受返回值
  //append方法的返回值和原来的值是否是相同的地址,取决于切片的容量是否足够,如果不够就会创建新的数组进行迁移
	s = append(s, "d")
	s = append(s, "e", "f")
	//[a b c d e f]
	fmt.Println(s)

	c := make([]string, len(s))
  //这里我们创建一个与 长度相同的copy空切片并将其复制到中
	copy(c, s)
	fmt.Println(c)

	l := s[2:5]
	//[c d e]
	fmt.Println(l)
	//可以在一行中声明并初始化切片的变量
	t := []string{"a", "b", "c"}
	t2 := []string{"a", "b", "c"}
	//t==t2,这个用于判断两个切片中的内容是否相同
	if slices.Equal(t, t2) {
		fmt.Println("t==t2")
	}
	//如果想比较两个的地址的话,可以使用返回或者比较两个的指针,不能直接用t==t2进行比较
  //t和t2的地址不同
	if reflect.ValueOf(t).Pointer() == reflect.ValueOf(t2).Pointer() {
		fmt.Println("t and t2 share the same underlying array")
	} else {
		fmt.Println("t and t2 do not share the same underlying array")
	}
	if &t[0] == &t2[0] {
		fmt.Println("t and t2 share the same underlying array")
	} else {
		fmt.Println("t and t2 do not share the same underlying array")
	}
	//切片可以组成多维数据结构。与多维数组不同,内部切片的长度可以变化
	twoD := make([][]int, 3)
	for i := 0; i < len(twoD); i++ {
		inner := i + 1
		twoD[i] = make([]int, inner)
		for j := 0; j < inner; j++ {
			twoD[i][j] = i + j
		}
	}
	//[[0] [1 2] [2 3 4]]
	fmt.Println(twoD)
}

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

import (
	"fmt"
	"maps"
)

func main() {
	m := make(map[string]int)
	m["k1"] = 7
	m["k2"] = 13
	//map[k1:7 k2:13]
	fmt.Println(m)

	v1 := m["k2"]
	fmt.Println(v1)

	v3 := m["k3"]
	//0,如果键不存在, 则返回值类型的零值
	fmt.Println(v3)

	fmt.Println(len(m))
	//内置命令delete从映射中删除键/值对
	delete(m, "k2")
	//map[k1:7]
	fmt.Println(m)
	//要从map中删除所有clear键/值对
	clear(m)
	//map[]
	fmt.Println(m)
	//从映射中获取值时,可选的第二个返回值指示映射中是否存在该键。
  //这可用于区分缺失键和具有零值的键(如0或 )""。
  //这里我们不需要值本身,因此我们用空白标识符 _忽略它
	_, prs := m["k2"]
	//false
	fmt.Println(prs)

	n := map[string]int{"foo": 1, "bar": 2}
	fmt.Println(n)
	//map[k:v k:v]请注意,使用 打印时,地图会以表格形式出现fmt.Println
	n2 := map[string]int{"foo": 1, "bar": 2}
	if maps.Equal(n, n2) {
		//true
		fmt.Println("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"

func plus(a int, b int) int {
	//Go 需要显式返回,即它不会自动返回最后一个表达式的值
	return a + b
}

func plusPlus(a, b, c int) int {
	return a + b + c
}

func vals() (int, int) {
	//可以多返回值
	return 3, 7
}

func main() {

	res := plus(1, 2)
	fmt.Println(res)

	res = plusPlus(1, 2, 3)
	fmt.Println(res)

	a, b := vals()
	fmt.Println(a, b)
	_, c := vals()
	//7
	fmt.Println(c)

}

闭包

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 main

import "fmt"

func intSeq() func() int {
	i := 0
	return func() int {
		i++
		return i
	}
}

func main() {
  //每次会创建一个新的状态
	nextInt := intSeq()
	//1
	fmt.Println(nextInt())
	//2
	fmt.Println(nextInt())
	//3
	fmt.Println(nextInt())
	newInt := intSeq()
	//1
	fmt.Println(newInt())
}

递归
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 fact(n int) int {
    if n == 0 {
       return 1
    }
    return n * fact(n-1)
}

func main() {
    fmt.Println(fact(5))
    var fib func(n int) int
    fib = func(n int) int {
       if n < 2 {
          return n
       }
       return fib(n-1) + fib(n-2)
    }
    fmt.Println(fib(10))
}
range函数
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"

func main() {
    nums := []int{1, 2, 3}
    sum := 0
    for _, num := range nums {
       sum += num
    }
    fmt.Println(sum)
    for i, num := range nums {
       if num == 3 {
          //2
          fmt.Println(i)
       }
    }
    kvs := map[string]int{"a": 1, "b": 2}
    for k, v := range kvs {
       fmt.Println(k, v)
    }
    for k := range kvs {
       fmt.Println(k)
    }

    for i, c := range "go" {
       fmt.Println(i, c)
    }
}
指针

&i语法给出了 的内存地址i,即 的指针i

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 zeroval(inval int) {
    inval = 0
}

func zeroptr(inptr *int) {
    *inptr = 0
}

func main() {
    i := 1
    fmt.Println(i)

    zeroval(i)
    fmt.Println(i)

    zeroptr(&i)
    fmt.Println(i)

}
strings

在 Go 中,rune 是一个类型,实际上是一个表示 Unicode 码点的整数(本质上是 int32 类型)。

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

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    const s = "Zbs"
    //3
    fmt.Println(len(s))
    for i := 0; i < len(s); i++ {
       //Z b s
       fmt.Printf("%c ", s[i])
    }
    fmt.Println()
    //3,用于计算字符串中 Unicode 字符(即 rune) 的数量
    fmt.Println(utf8.RuneCountInString(s))

    //如果没有%c,就会输出ascii的int值
    for idx, value := range s {
       //0 90
       //1 98
       //2 115
       fmt.Println(idx, value)
    }

    for i, w := 0, 0; i < len(s); i += w {
       //用于解码字符串中的下一个 Unicode 字符(rune),并返回该字符以及它的字节宽度
       runValue, width := utf8.DecodeRuneInString(s[i:])
       fmt.Println(width, runValue)
       w = width
       examineRunne(runValue)
    }

}
func examineRunne(r rune) {
    if r == 'b' {
       fmt.Println("b")
    } else if r == 'a' {
       fmt.Println("a")
    }
}
结构体

如果要将结构体作为函数的参数进行传递的时候,要考虑是否要修改原结构体的值,来确定结构体参数设置为指针类型还是值类型

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"

type person struct {
    name string
    age  int
}

func newPerson(name string, age int) *person {
    return &person{name, age}
}

func main() {
    fmt.Println(person{"Bob", 20})
    fmt.Println(person{name: "Bob", age: 20})
    fmt.Println(person{name: "Bob"})
    //&{Bob 20}
    fmt.Println(&person{name: "Bob", age: 20})
    //&{Bob 20}
  	//将新结构体的创建封装在构造函数中是一种惯用做法
    fmt.Println(newPerson("Bob", 20))

    s := person{name: "Bob", age: 20}
    fmt.Println(s.name)

    sp := &s
    //20
    fmt.Println(sp.age)
    sp.age = 30
    //30
    fmt.Println(s.age)

    dog := struct {
       name   string
       isGood bool
    }{
       "name",
       true,
    }
    fmt.Println(dog)
}
方法

通过结构体、方法、接口来实现面对对象的设计

通过结构体+方法实现类的组合

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"

type rect struct {
    width, height int
}
//指针型接收者,这样方法内部的修改会影响到结构体的原字段
func (r *rect) area() int {
    return r.width * r.height
}
//值接收者,方法内部的修改不会影响到结构体的原字段
func (r rect) perim() int {
    return 2*r.width + 2*r.height
}

func main() {
    r := rect{width: 10, height: 5}

    fmt.Println("area: ", r.area())
    fmt.Println("perim:", r.perim())
		//Go 自动处理方法调用时值与指针之间的转换
    rp := &r
    fmt.Println("area: ", rp.area())
    fmt.Println("perim:", rp.perim())
  	//这里如果定义的是指针类型,那么r就只能传递的是地址
    var t *rect
    t = &r
    t.width = 0
    fmt.Println("area: ", t.area())
    fmt.Println("perim:", t.perim())
    fmt.Println("area: ", r.area())
    fmt.Println("perim:", r.perim())
}
接口

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

import (
    "fmt"
    "math"
)

type geometry interface {
    area() float64
    perim() float64
}

type rectt struct {
    width, height float64
}

type circle struct {
    radius float64
}
//在 Go 中,接口的实现依赖于方法接收者的类型,而方法的调用就需要按照接收者的类型来进行调用
func (r *rectt) area() float64 {
    return r.width * r.height
}
func (r *rectt) perim() float64 {
    return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}
//如果变量具有接口类型,那么我们可以调用指定接口中的方法
//类似于java中的多态
func measure(g geometry) {
    fmt.Println(g)
}

func main() {
    r := rectt{width: 10, height: 5}
    c := circle{radius: 5}
  //circle和结构类型rect都实现了geometry接口,因此我们可以使用这些结构的实例作为的参数measure
  //因为实现接口的方法接收者是指针类型,所以这里就需要赋地址
    measure(&r)
    measure(c)
}
枚举

枚举是一种具有固定数量可能值的类型,每个值都有不同的名称。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
package main

import "fmt"

type ServerState int

const (
	//这里通过 iota 创建了四个常量,分别表示服务器的四种状态
	//iota 是 Go 中用于生成常量值的一个特殊关键字,它在每一行常量定义中会自动递增
	StateIdle ServerState = iota
	StateConnected
	StateError
	StateRetrying
)

var stateName = map[ServerState]string{
	StateIdle:      "idle",
	StateConnected: "connected",
	StateError:     "error",
	StateRetrying:  "retrying",
}

func (ss ServerState) String() string {
	return stateName[ss]
}

func main() {
	//这里虽然ServerState是int类型,但不能传int类型进去,否则编译器会报错,因为它们的类型不同。
	ns := transition(StateIdle)
	fmt.Println(ns)

	ns2 := transition(ns)
	fmt.Println(ns2)
}

// transition 模拟服务器的状态转换;它采用现有状态并返回新状态
func transition(s ServerState) ServerState {
	switch s {
	case StateIdle:
		return StateConnected
	case StateConnected, StateRetrying:

		return StateIdle
	case StateError:
		return StateError
	default:
		panic(fmt.Errorf("unknown state: %s", 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
package main

import "fmt"

type base struct {
    num int
}

func (b base) describe() string {
    return fmt.Sprintf("base with num=%v", b.num)
}

type container struct {
  	//Acontainer 嵌入a base。嵌入看起来像一个没有名称的字段
    base
    str string
}

func main() {
		//当使用文字创建结构时,我们必须明确初始化嵌入;这里嵌入的类型用作字段名称
    co := container{
       base: base{
          num: 1,
       },
       str: "some name",
    }
		//这样就可以直接访问子结构体的子字段 例如co.num
    fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)

    fmt.Println("also num:", co.base.num)
		//由于container嵌入base, 的方法 base也成为 的方法。这里我们 直接container在 上调用嵌入的方法
    fmt.Println("describe:", co.describe())

    type describer interface {
       describe() string
    }
		//嵌入带有方法的结构可用于将接口实现赋予其他结构。这里我们看到 acontainer现在实现了 describer接口,因为它嵌入了base。
    var d describer = co
    fmt.Println("describer:", d.describe())
}
泛型
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
package main

import "fmt"

// SlicesIndex 使用了 类型约束,其中 S ~[]E 和 E comparable 是函数的类型参数约束
// S 必须是 切片类型,并且该切片的元素类型是 E
// ~ 运算符在这里的作用是允许类型别名,即允许 S 不仅仅是 []E 这样的原始切片类型,也可以是一个切片类型的类型别名,即如果后面使用这个方法的时候,传递参数时切片的类型不仅可以是E,也可以是其他类型
func SlicesIndex[S ~[]E, E comparable](s S, v E) int {
    //E comparable 限制了传入的元素类型 E 必须是可以通过 == 和 != 运算符进行比较的类型
    for i := range s {
       if v == s[i] {
          return i
       }
    }
    return -1
}

// List List[T any] 是一个泛型链表类型,T 表示链表中节点的元素类型,any 表示可以是任何类型
type List[T any] struct {
    head, tail *element[T]
}

// element[T any] 是链表中的节点类型
type element[T any] struct {
  //带的*说明是地址
    next *element[T]
    val  T
}
//为链表定义了两个方法
//我们可以像在常规类型上一样在泛型类型上定义方法,但必须保留类型参数。类型是List[T],而不是List
func (lst *List[T]) Push(v T) {
    if lst.tail == nil {
       lst.head = &element[T]{val: v}
       lst.tail = lst.head
    } else {
       lst.tail.next = &element[T]{val: v}
       lst.tail = lst.tail.next
    }
}

func (lst *List[T]) AllElements() []T {
    var elems []T
    for e := lst.head; e != nil; e = e.next {
       elems = append(elems, e.val)
    }
    return elems
}

func main() {
    var s = []string{"foo", "bar", "zoo"}

    fmt.Println("index of zoo:", SlicesIndex(s, "zoo"))

    _ = SlicesIndex[[]string, string](s, "zoo")

    lst := List[int]{}
    lst.Push(10)
    lst.Push(13)
    lst.Push(23)
    fmt.Println("list:", lst.AllElements())
}
range迭代器

迭代器函数将另一个函数作为参数,yield按照惯例调用该函数(但名称可以任意)。它将调用yield我们想要迭代的每个元素,并注意yield的返回值以防可能提前终止。

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

import (
	"fmt"
	"iter"
	"slices"
)

type List[T any] struct {
	head, tail *element[T]
}

type element[T any] struct {
	next *element[T]
	val  T
}

func (lst *List[T]) Push(v T) {
	if lst.tail == nil {
		lst.head = &element[T]{val: v}
		lst.tail = lst.head
	} else {
		lst.tail.next = &element[T]{val: v}
		lst.tail = lst.tail.next
	}
}

func (lst *List[T]) All() iter.Seq[T] {
	return func(yield func(T) bool) {

		for e := lst.head; e != nil; e = e.next {
			if !yield(e.val) {
				return
			}
		}
	}
}
//迭代(Iteration) 不一定依赖于特定的数据结构,甚至不需要是有限的(比如无限序列)
func genFib() iter.Seq[int] {
	return func(yield func(int) bool) {
		a, b := 1, 1

		for {
			if !yield(a) {
				return
			}
			a, b = b, a+b
		}
	}
}

func main() {
	lst := List[int]{}
	lst.Push(10)
	lst.Push(13)
	lst.Push(23)
	//由于List.All返回一个迭代器,我们可以在常规range循环中使用它
	for e := range lst.All() {
		fmt.Println(e)
	}
	//像切片这样的包有许多有用的函数可以与迭代器一起使用
	all := slices.Collect(lst.All())
	fmt.Println("all:", all)
	
	for n := range genFib() {

		if n >= 10 {
			break
		}
		fmt.Println(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
package main

import "fmt"

type ListNode struct {
	next *ListNode
	val  int
}

func main() {
	head := &ListNode{}
	node := head
	for node != nil {
		if node.next == nil {
			node.next = &ListNode{nil, 1}
			break
		}
		node = node.next
	}
	for head != nil {
		fmt.Println(head.val)
		head = head.next
	}
	
}

错误

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

import (
	"errors"
	"fmt"
)

func f(arg int) (int, error) {
	if arg == 42 {
		//errors.Newerror使用给定的错误消息构造一个基本值
		return -1, errors.New("can't work with 42")
	}

	return arg + 3, nil
}
//标记错误是一个预先声明的变量,用于表示特定的错误情况
var ErrOutOfTea = fmt.Errorf("no more tea available")
var ErrPower = fmt.Errorf("can't boil water")

func makeTea(arg int) error {
	if arg == 2 {
		return ErrOutOfTea
	} else if arg == 4 {
		//这里是包装错误,因为在平时使用的过程中,希望不仅返回错误,还可以返回错误上下文信息
    //将一个错误包装到另一个错误中,并且保留原始错误信息,这样可以构建一个错误链
    //这里的函数makeTea就是一个错误,根据传递的参数来模拟出不同的错误
		return fmt.Errorf("making tea: %w", ErrPower)
	}
	return nil
}

func main() {
	for _, i := range []int{7, 42} {

		if r, e := f(i); e != nil {
			fmt.Println("f failed:", e)
		} else {
			fmt.Println("f worked:", r)
		}
	}

	for i := 1; i <= 5; i++ {
		if err := makeTea(i); err != nil {
			//errors.Is检查给定错误(或其链中的任何错误)是否与特定错误值匹配
			if errors.Is(err, ErrOutOfTea) {
				fmt.Println("We should buy new tea!")
			} else if errors.Is(err, ErrPower) {
				fmt.Println("Now it is dark.")
			} else {
				fmt.Printf("unknown error: %s\n", err)
			}
			continue
		}

		fmt.Println("Tea is ready!")
	}
}

自定义错误
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 (
	"errors"
	"fmt"
)

// 使用结构体自定义一个错误
type argError struct {
	arg     int
	message string
}

// 实现error接口
func (e *argError) Error() string {
	return fmt.Sprintf("%d - %s", e.arg, e.message)
}

func f1(arg int) (int, error) {
	if arg == 42 {
		//方法的接收者使用的是指针类型,所以这里要用&
		return -1, &argError{arg, "can't work with it"}
	}
	return arg + 3, nil
}

func main() {

	_, err := f1(42)
	var ae *argError
	//这里的errors.As会检查err是否是ae类型的错误,如果是的话,就会将err赋值给ae
	//errors.As赋值的时候,要求被赋值的变量必须是指针类型,这样才可以进行修改
	//所以这里传递的是ae的地址,尽管ae本来就是一个引用地址
	if errors.As(err, &ae) {
		fmt.Println(ae.arg)
		fmt.Println(ae.message)
	} else {
		fmt.Println("err doesn't match argError")
	}
}
/*
这里最后的输出是
42
can't work with it
*/

并发

Goroutines

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
31
32
33
package main

import (
	"fmt"
	"time"
)

func ff(from string) {
	for i := 0; i < 3; i++ {
		fmt.Println(from, ":", i)
	}
}

func main() {
	//这是一个普通的函数调用,运行在 main goroutine 中
	//因此它会顺序执行并完成后才继续向下执行
	ff("direct")

	//main 线程不会等待它完成,直接继续执行下一行代码。
	go ff("goroutine")

	//启动一个 goroutine 进行匿名函数调用
	go func(msg string) {
		fmt.Println(msg)
	}("going")

	//这个 Sleep 的目的是给 goroutine 运行的时间
	//否则 main 可能会直接结束,导致 goroutine 没机会运行
	//即使是goroutine正在执行,只要主线程结束,goroutine也会被中断
	time.Sleep(time.Second)
	fmt.Println("done")
}

但是这里使用的sleep方法不太好,最好还是使用waitgroup

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

func ff(from string) {
	for i := 0; i < 3; i++ {
		fmt.Println(from, ":", i)
	}
}

func main() {
	//使用waitgroup的方式
	var wg sync.WaitGroup

	ff("direct")
	//添加两个要等待的goroutine
	wg.Add(2)

	go func() {
		//这里需要加wg.Done(),用于wg的计数,要两次主线程才会结束等待,否则就会死锁
		defer wg.Done()
		ff("goroutine")
	}()

	go func(msg string) {
		defer wg.Done()
		fmt.Println(msg)
	}("going")
	//主线程在这个位置等待
	wg.Wait()
	fmt.Println("done")
}

Channels

Channel用于goroutine之间的消息通信

无缓冲通道会阻塞发送方和接收方,会一直等待发送方和接收方都准备好了,两边才会解除阻塞,进行执行下去

下面执行过程分析

  • Step 1: go func() { messages <- "ping" }() 启动一个 goroutine,并试图发送 "ping"
  • Step 2: 由于 messages 是无缓冲通道,messages <- "ping" 不能立即完成,它必须等 msg := <-messages 执行时,才会真正发送数据。
  • Step 3: msg := <-messagesmain goroutine 中执行,此时 messages <- "ping" 解除阻塞,数据传递发生,两个 goroutine 同时继续执行。
  • Step 4: "ping" 被打印,程序结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {

	//这创建了一个无缓冲通道,默认情况下,发送和接收操作都会阻塞,直到对方准备好
	messages := make(chan string)

	//创建一个goroutine,将消息传入通道
	//由于 messages 是无缓冲通道,发送操作会阻塞,直到 main 线程接收数据
	go func() { messages <- "ping" }()
	//将消息从通道中取出来
	//main 线程在这里阻塞,等待 messages 通道里有数据
	msg := <-messages
	fmt.Println(msg)
}

缓冲通道

🔹 无缓冲通道 = 生产者 & 消费者必须同时就绪

🔹 带缓冲通道 = 允许生产者先存储数据,消费者稍后取出

对于缓冲通道而言,如果通道为空,那就会阻塞消费者,如果通道是满的,就会阻塞生产者

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

func main() {
	messages := make(chan string, 2) // 创建一个缓冲区大小为 2 的通道

	// 生产者 goroutine
	go func() {
		messages <- "hello"
		fmt.Println("Sent: hello") // 立即执行,不会阻塞
		messages <- "world"
		fmt.Println("Sent: world") // 立即执行,不会阻塞
		// 由于缓冲区已满,下面的发送会阻塞,直到有消费者接收数据
		messages <- "!"
		fmt.Println("Sent: !") // 这行代码会等待
	}()

	// 让生产者先执行一会儿
	time.Sleep(2 * time.Second)

	// 消费者取出数据
	fmt.Println("Received:", <-messages)
	fmt.Println("Received:", <-messages)
	fmt.Println("Received:", <-messages) // 这里才会解除第三个消息的阻塞
}

正常情况下生产者消费者 只能一次发送或接收一个数据,但也可以通过传切片、或者使用range 通道的方式一次传递或者接收多个数据

通道同步

通过让主线程作为接收者阻塞接收消息来达到线程的同步

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

func worker(done chan bool) {
	fmt.Println("worker")
	time.Sleep(time.Second)
	fmt.Println("done")

	done <- true

}

func main() {
	done := make(chan bool, 1)
	go worker(done)
	<-done

}

通道方向

当使用通道作为函数参数时,可以指定通道的方向,来提高程序的安全性

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

import "fmt"

func ping(pings chan<- string, msg string) {
	pings <- msg
}

func pong(pings <-chan string, pongs chan<- string) {
	msg := <-pings
	pongs <- msg
}

func main() {
	pings := make(chan string, 1)
	pongs := make(chan string, 1)
	ping(pings, "passed message")
	pong(pings, pongs)
	fmt.Println(<-pongs)
}

select

将channel和select结合起来

select 语句使得一个 goroutine 可以等待多个 channel 的操作(接收或发送)。它会阻塞直到其中一个 case 可以执行(即某个 channel 完成了接收或发送)。如果有多个 case 同时准备好,select 会随机选择其中一个执行

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

func main() {

    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()
		//如果单独接收两个那就会3s,而使用select会并行处理两个通道,这样只会用2s
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}
超时

这里是模拟一个超时的场景,即在调用其他应用的过程中,虽然需要该应用的返回值,但是主线程有极其严格的时间要求,所以当超过规定时间后,主线程就会决定进行超时处理,放弃返回值

这里使用到的就是select,<-time.After 等待在 1 秒的超时后发送值

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

func main() {
	//这里使用的缓冲通道,这样goroutine在将数据放入通道后,就可以结束
	c1 := make(chan string,1)
	go func() {
		//模型应用需要2s才能返回结果
		time.Sleep(2 * time.Second)
		c1 <- "result 1"
	}()
	//select是每次只会选择一个case里面的执行,然后就会跳过
	//所以这里即便res没有从通道中接收值,也不会阻塞,在执行过超时处理之后,就会往下进行执行
	select {
	case res := <-c1:
		fmt.Println(res)
    //如果超过1s,就会执行这条语句,进行超时处理
	case <-time.After(1 * time.Second):
		fmt.Println("timeout 1")
	}

	c2 := make(chan string, 1)
	go func() {
		time.Sleep(2 * time.Second)
		c2 <- "result 2"
	}()
	select {
	case res := <-c2:
		fmt.Println(res)
	case <-time.After(3 * time.Second):
		fmt.Println("timeout 2")
	}
}

/*
timeout 1
result 2
*/
非阻塞通道操作

select 还可以配合 default 语句来实现 非阻塞操作。如果所有的 case 都不能立刻执行,select 会进入 default,避免阻塞

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() {
	messages := make(chan string)
	signals := make(chan bool)

	select {
	case msg := <-messages:
		fmt.Println("received message", msg)
	default:
		fmt.Println("no message received")
	}

	msg := "hi"
	select {
	case messages <- msg:
		fmt.Println("sent message", msg)
	default:
		fmt.Println("no message sent")
	}

	select {
	case msg := <-messages:
		fmt.Println("received message", msg)
	case sig := <-signals:
		fmt.Println("received signal", sig)
	default:
		fmt.Println("no activity")
	}
}

关闭通道

关闭通道可以用于不知道通道内容有多少,要使用for循环一直接收的情况

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() {
	jobs := make(chan int, 5)
	done := make(chan bool)

	go func() {
    //这里使用死循环,一直从通道中获取值
    //只有通道已经关闭,且没有剩余数据时的时候more就会为false
		for {
      //使用的是有缓冲的通道,所以作为接收者,当通道有内容就会进行下去,没有就会阻塞
			j, more := <-jobs
			if more {
				fmt.Println("received job", j)
			} else {
				fmt.Println("received all jobs")
				done <- true
				return
			}
		}
	}()
	//因为使用的是缓冲通道,所以可以一次性全放进通道,往下走下去
	for j := 1; j <= 3; j++ {
		jobs <- j
		fmt.Println("sent job", j)
	}
  //关闭通道
	close(jobs)
	fmt.Println("sent all jobs")
	//使用通道阻塞的方式等goroutine结束
	<-done
	//只有通道已经关闭,且没有剩余数据时,ok为false
	_, ok := <-jobs
	fmt.Println("received more jobs:", ok)
}
range通道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {

	queue := make(chan string, 2)
	queue <- "one"
	queue <- "two"
	close(queue)
	//关闭通道后,也可以继续遍历,从通道中取值
	for elem := range queue {
		fmt.Println(elem)
	}
}

计数器

我们经常希望在未来的某个时间点执行 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
39
40
41
package main

import (
	"fmt"
	"time"
)

func main() {

	//创建了一个定时器 timer1,它将在 2 秒后触发,并通过 timer1.C 发送一个值
	timer1 := time.NewTimer(2 * time.Second)
	//用于阻塞当前 goroutine,直到定时器的 channel C 被触发(也就是 2 秒后)
	<-timer1.C
	fmt.Println("Timer 1 fired")

	timer2 := time.NewTimer(2 * time.Second)
	go func() {
		//然后启动一个新的 goroutine 来监听 timer2.C
		//这里如果定时器是被关闭的,通道就会立刻关闭,返回0值,后面的就不执行了
		fmt.Println("1")
		//语句立即返回并继续执行代码,defer 语句确实没有机会执行
		defer fmt.Println("2")
		<-timer2.C
		fmt.Println("Timer 2 fired")
	}()
	//调用会尝试停止定时器。
	//如果定时器还没有触发,它会成功停止,返回 true。
	//如果定时器已经触发,它会返回 false
	time.Sleep(time.Second)
	stop2 := timer2.Stop()
	if stop2 {
		fmt.Println("Timer 2 stopped")
	}

	time.Sleep(2 * time.Second)
}
/*
Timer 1 fired
1
Timer 2 stopped
*/
ticker

timer用于想未来某段时间后做一件事,ticker用于想要定期重复做某件事

ticker和timer有类似的机制,通过通道来发送消息,表示状态

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

func main() {

    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)

    go func() {
       for {
          select {
          case <-done:
             return
          //每隔500 * time.Millisecond发送一次
          case t := <-ticker.C:
             fmt.Println("Tick at", t)
          }
       }
    }()
		//主线程会在这阻塞一段时间,以便ticker多发送几次
    time.Sleep(1600 * time.Millisecond)
  	//关闭ticker
    ticker.Stop()
    done <- true
    fmt.Println("Ticker stopped")
}
worker-pools

主要用于处理大量并发任务,同时又要控制资源使用

它解决的核心问题是:有很多任务要处理,但不能无限制地开启 goroutine 或线程,否则会耗尽资源

本质上相当于java中的线程池,通过通道来传递任务,这段代码的作用就是创建了三个线程,不断的从jobs中取任务执行

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

import (
    "fmt"
    "time"
)

func worker1(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
       fmt.Println("worker", id, "started job", j)
       time.Sleep(time.Second)
       fmt.Println("worker", id, "finished job", j)
       results <- j * 2
    }
}

func main() {
    const numJobs = 5
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    for w := 1; w <= 3; w++ {
      //启动了 3 个并发的 worker(worker1 ~ worker3),它们共享同一个 jobs 通道
       go worker1(w, jobs, results)
    }
    for j := 1; j <= numJobs; j++ {
      //向 jobs 通道发送 5 个任务(1 到 5)
       jobs <- j
    }
  	//发送完任务后关闭 jobs 通道,通知所有 worker 没有任务了
    close(jobs)
    for a := 1; a <= numJobs; a++ {
      //主线程逐次从通道中拿取结果,主要用于阻塞主线程
       <-results
    }
}
WaitGroups

要等待多个 goroutine 完成,我们可以使用WaitGroups

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

func worker2(id int) {
	fmt.Printf("Worker %d starting\n", id)

	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {

	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		//启动多个 goroutine 并增加每个 goroutine 的 WaitGroup 计数器
		wg.Add(1)
		//创建一个闭包,这个的目的是因为waitgroup需要使用done才能算结束一个
		//所以是需要defer wg.Done(),为了解偶,不将无关代码写到worker2中,所以就另创建了一个func
		go func() {
			defer wg.Done()
			worker2(i)
		}()
	}

	wg.Wait()

}

限流

在服务端处理请求时,限流用于防止服务过载。它通过控制单位时间内处理的请求数量,避免系统资源被耗尽。

Go 的 time.Tickchannel 的组合可以实现非常简洁的限流逻辑。

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

import (
    "fmt"
    "time"
)

func main() {

    //1、基本限流
    //创建一个缓冲通道 requests,模拟要处理的 5 个请求。
    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
       requests <- i
    }
    //用于关闭通道,当你通过 range 遍历通道时,如果不关闭通道,接收方就无法知道数据何时结束
    close(requests)
    //limiter 是一个定时器通道,每 200ms 发出一个事件。作用是限制处理速率为 每 200ms 一个请求
    limiter := time.Tick(200 * time.Millisecond)

    //每次处理请求前,从 limiter 中读取一个值,相当于“等待许可”,确保处理频率不会超过限制
    for req := range requests {
       <-limiter
       fmt.Println("request", req, time.Now())
    }
    //2、突发限流
    //创建一个限制器,类似于创建一个数量为3的令牌桶
    burstyLimiter := make(chan time.Time, 3)

    for i := 0; i < 3; i++ {
       burstyLimiter <- time.Now()
    }
    //后台协程每 200ms 向 burstyLimiter 添加一个许可(如果没满),以便持续补充“突发令牌”。
    go func() {
       for t := range time.Tick(200 * time.Millisecond) {
          burstyLimiter <- t
       }
    }()
    //创建一个大小为5的任务需求
    burstyRequests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
       burstyRequests <- i
    }
    close(burstyRequests)
    //遍历任务需求,只有在令牌桶中有令牌的时候,才会去执行任务,以此来限流
    for req := range burstyRequests {
       <-burstyLimiter
       fmt.Println("request", req, time.Now())
    }
}
原子计数器

用并发安全的方式统计一个变量在多协程中被累加的总次数

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"
    "sync"
    "sync/atomic"
)

func main() {

    //定义一个 atomic.Uint64 类型变量 ops
    //确保多线程环境下读写变量是原子的,即不可分割的操作,不会产生竞争条件(data race)
    var ops atomic.Uint64

    var wg sync.WaitGroup

    for i := 0; i < 50; i++ {
       wg.Add(1)

       go func() {
          for c := 0; c < 1000; c++ {
						
             ops.Add(1)
          }

          wg.Done()
       }()
    }

    wg.Wait()
		//50000
    fmt.Println("ops:", ops.Load())
}
互斥锁

通过 sync.Mutex 来实现互斥锁,确保多个 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
    "fmt"
    "sync"
)

type Container struct {
    //互斥锁,保护 counters 变量
    mu sync.Mutex
    //存储计数器的 map,键是字符串,值是对应的计数
    counters map[string]int
}

func (c *Container) inc(name string) {
    //加锁,防止其他 goroutine 同时访问 counters
    //这样保证同一时间只有一个线程可以访问map
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

func main() {
    c := Container{

       counters: map[string]int{"a": 0, "b": 0},
    }

    var wg sync.WaitGroup
    //doIncrement 是一个闭包(closure),用于递增指定 name 的计数器 n 次
    doIncrement := func(name string, n int) {
       for i := 0; i < n; i++ {
          c.inc(name)
       }
       wg.Done()
    }

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

    wg.Wait()
    fmt.Println(c.counters)
}
状态 Goroutines

在go中不仅可以使用互斥锁来实现多线程下资源共享,还可以使用goroutine 和通道的内置同步功能来实现相同的结果,通过通信并让每段数据都归 1 个 goroutine 所有

这种方式实现并发安全的原理是通过单个goroutine的func来管理state这个资源,其他多个goroutines想要访问都是通过将请求放入对应的通道中,由这个单个的goroutinue进行处理

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
83
84
85
86
87
88
89
90
91
92
package main

import (
    "fmt"
    "math/rand"
    "sync/atomic"
    "time"
)

// 用于读取 map 的操作
type readOp struct {
    key int
    //用于返回读取的值
    resp chan int
}

// 用于写入 map 的操作
type writeOp struct {
    key int
    val int
    //用于确认写入成功
    resp chan bool
}

func main() {
    //通过 atomic 来进行原子计数,确保并发安全
    var readOps uint64
    var writeOps uint64
    //用于接收读请求的通道
    reads := make(chan readOp)
    //用于接收写请求的通道
    writes := make(chan writeOp)
  	//这个函数的作用就是不断接收请求,对state进行修改
    go func() {
       //维护一个 map[int]int 作为共享状态
       var state = make(map[int]int)
       for {
          //通过 select 来监听 reads 和 writes 通道
          select {
          //接收到读请求后,将state中对应的值传给resp
          case read := <-reads:
             read.resp <- state[read.key]
          //接收到写请求后,将要写的值记录在state中,并修改通道中的状态
          case write := <-writes:
             state[write.key] = write.val
             write.resp <- true

          }
       }
    }()
    for r := 0; r < 100; r++ {
       go func() {
          for {
             //创建一个读请求
             read := readOp{
                key:  rand.Intn(5),
                resp: make(chan int),
             }
             //发送读请求
             reads <- read
             //等待返回值
             <-read.resp
             atomic.AddUint64(&readOps, 1)
             time.Sleep(time.Millisecond)
          }
       }()
    }
    for w := 0; w < 10; w++ {
       go func() {
          for {
             //创建一个写请求
             write := writeOp{
                key:  rand.Intn(5),
                val:  rand.Intn(100),
                resp: make(chan bool)}
             //发送写请求
             writes <- write
             <-write.resp
             atomic.AddUint64(&writeOps, 1)
             time.Sleep(time.Millisecond)
          }
       }()
    }

    time.Sleep(time.Second)
    //获取readOps的值
    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)

}

排序

排序