1. 什么是Go语言

  • Go是由Google开发的一种开源编程语言,设计初衷是让开发者可以快速构建高效的现代应用。Go结合了静态语言的性能和动态语言的开发效率,特别适合开发高并发的后端服务

2. 包的概念

  • Go 用包 (package) 来组织代码,和 Python 的模块差不多。把相关功能的代码放一个目录,就是一个包。包可以被其他包引用,方便复用。

  • 有一个特殊的包叫 main,它是程序的入口。每个可执行程序必须有且只有一个 main 包,里面必须有一个 main() 函数。

package main  // 声明这是 main 包

import "fmt" // 引入 fmt 包,用于打印输出

func main() {
fmt.Println("hello go")
fmt.Println("hello haha")
}

几个注意点:

  • 每个 .go 文件开头必须声明属于哪个包
  • 左花括号 { 必须和函数名同一行,不能换行 (Go 的强制风格)
  • 语句结尾不用加分号
  • 注释用 ///* */

3. 变量声明

Go 声明变量有三种写法:

var name string = "Hongda"   // 完整写法: var + 名字 + 类型 + 值
var name = "Hongda" // 省略类型,编译器自动推断
name := "Hongda" // 短声明,最常用

短声明 := 写起来最简洁,日常用得最多。但它只能在函数内部用,包级别的变量必须用 var

var globalVar = "我是全局变量"  // 包级别,必须用 var

func main() {
localVar := "我是局部变量" // 函数内,短声明 OK
}

零值: 变量声明了但没赋值,Go 会给默认值,叫”零值”。

var count int     // 0
var text string // ""(空字符串)
var flag bool // false

批量声明: 多个变量可以放一起声明。

// 同类型
var i, j, k int

// 不同类型,自动推断
var a, b, c = 1, "hello", true

// 用括号组织,适合包级别变量
var (
host = "localhost"
port = 5432
)

多变量赋值: Go 支持一行给多个变量赋值,交换变量值特别方便。

x, y := 10, 20    // 声明并赋值
x, y = y, x // 交换,不需要临时变量

注意 := 是声明+赋值,= 是纯赋值。已经声明过的变量再用 := 会报错。


4. 赋值

Go 的赋值操作很直接:

x = 1              // 基本赋值
*p = true // 指针解引用赋值
person.name = "bob" // 结构体字段赋值

复合赋值: 简化 x = x + 5 这种写法。

count[x] *= scale  // 等价于 count[x] = count[x] * scale
x += 5 // 等价于 x = x + 5

自增自减: 注意 Gov++ 是语句不是表达式,不能写 x = i++

v := 1
v++ // v 变为 2
v-- // v 变为 1

// x = i++ // 错误! Go 不允许这样写

元组赋值

可以一行给多个变量赋值,右边表达式先全部求值,再赋给左边。

// 交换变量,不需要临时变量
x, y = y, x

// 最大公约数
func gcd(x, y int) int {
for y != 0 {
x, y = y, x%y
}
return x
}

// 斐波那契
func fib(n int) int {
x, y := 0, 1
for i := 0; i < n; i++ {
x, y = y, x+y
}
return x
}

函数多返回值: Go 函数可以返回多个值,常见的模式是返回结果和错误。

f, err := os.Open("foo.txt")

ok 模式: map 查找、类型断言、channel 接收都可以返回第二个 bool 值表示是否成功。

v, ok := m[key]    // map 查找
v, ok := x.(T) // 类型断言
v, ok := <-ch // channel 接收

丢弃不需要的值: 用 _ 占位。

_, err := io.Copy(dst, src)  // 只关心 err,不关心拷贝了多少字节

可赋值性

赋值分显式和隐式两种:

// 显式赋值
x = 1

// 隐式赋值 - 函数调用时参数传递
func add(a, b int) {} // 调用时实参赋给 a, b

// 隐式赋值 - 字面量初始化
medals := []string{"gold", "silver", "bronze"}

赋值规则: 类型必须匹配。nil 可以赋给指针、slicemapchannelinterface 等引用类型。

var p *int = nil
var m map[string]int = nil

5. 函数基础

func 关键字声明函数。函数名首字母小写是包内私有,大写是公开可导出。

func sayHello() {
fmt.Println("Hello")
}

参数

参数格式是 变量名 类型,多个参数用逗号分隔。Go 不支持默认参数。

func greet(name string, age int) {
fmt.Printf("%s is %d years old\n", name, age)
}

// 相同类型可以简写
func add(a, b int) int {
return a + b
}

不定参数: 用 ... 表示,必须放在最后。

func sum(nums ...int) int {
total := 0
for _, v := range nums {
total += v
}
return total
}

sum() // 0 个参数
sum(1) // 1 个参数
sum(1, 2, 3) // 3 个参数

返回值

单个返回值不用括号,多个要括号。

// 单个返回值
func double(x int) int {
return x * 2
}

// 多个返回值
func swap(x, y string) (string, string) {
return y, x
}

// 命名返回值,可以直接 return
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}

丢弃不需要的返回值: 用 _ 占位。

max, _ := MaxAndMin(10, 20)  // 只要 max,不要 min

函数调用流程

函数调用是栈结构,后进先出 (LIFO)。

func funcc(c int) {
fmt.Println("c =", c)
}

func funcb(b int) {
funcc(b - 1)
fmt.Println("b =", b)
}

func funca(a int) {
funcb(a - 1)
fmt.Println("a =", a)
}

func main() {
funca(3)
fmt.Println("main")
}

执行流程:

main() → funca(3) → funcb(2) → funcc(1)
打印 "c = 1"
打印 "b = 2"
打印 "a = 3"
打印 "main"

输出: c = 1b = 2a = 3main

递归

函数调用自身,必须有退出条件。

// 累加: 1 + 2 + ... + n
func sum(n int) int {
if n == 1 {
return 1
}
return n + sum(n-1)
}

sum(100) // 5050

执行流程:

sum(100) = 100 + sum(99)
= 100 + 99 + sum(98)
= ...
= 100 + 99 + ... + 1
= 5050

6. 函数进阶

函数类型

函数在 Go 里是一等公民,可以赋值给变量、作为参数传递。

func Add(a, b int) int {
return a + b
}

func Minus(a, b int) int {
return a - b
}

// 定义函数类型
type CalcFunc func(int, int) int

func main() {
var f CalcFunc

f = Add
fmt.Println(f(10, 5)) // 15

f = Minus
fmt.Println(f(10, 5)) // 5
}

函数作为参数 (回调函数): 实现多态,同一个函数传入不同的函数参数,实现不同功能。

type CalcFunc func(int, int) int

func Calc(a, b int, f CalcFunc) int {
return f(a, b)
}

func main() {
Calc(3, 3, Add) // 6,加法
Calc(3, 3, Minus) // 0,减法
}

匿名函数

没有名字的函数,可以赋值给变量,也可以定义后立即调用。

// 赋值给变量
f := func(x, y int) int {
return x + y
}
f(1, 2) // 3

// 定义并立即调用
func(x, y int) {
fmt.Println(x + y)
}(1, 2)

闭包

闭包是引用了外部变量的匿名函数。关键点: 变量以引用方式捕获,会保留状态。

// 普通函数: 每次调用 a 都重新初始化
func test01() int {
var a int // 每次都是 0
a++
return a * a
}
// 调用 5 次输出: 1 1 1 1 1

// 闭包: a 只初始化一次,状态保留
func test02() func() int {
var a int
return func() int {
a++
return a * a
}
}

func main() {
f := test02()
fmt.Println(f()) // 1
fmt.Println(f()) // 4
fmt.Println(f()) // 9
fmt.Println(f()) // 16
}

defer

defer 延迟执行,函数返回前才执行。常用于资源清理: 关闭文件、释放连接等。

func main() {
defer fmt.Println("最后执行")
fmt.Println("先执行")
}
// 输出:
// 先执行
// 最后执行

典型用法:

func readFile() {
f, err := os.Open("file.txt")
if err != nil {
return
}
defer f.Close() // 确保文件关闭
// 处理文件...
}

多个 defer: 后进先出 (LIFO),先定义的后执行。

func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
}
// 输出: 3 2 1

defer 与闭包: 注意捕获变量的时机。

func main() {
a := 10

// 无参数: 捕获变量,使用最终值
defer func() {
fmt.Println(a) // 100
}()

// 有参数: 定义时传值,使用当时的值
defer func(x int) {
fmt.Println(x) // 10
}(a)

a = 100
}
// 输出:
// 10
// 100

总结: 想要 defer 时的值,就传参数; 想要最终值,就用闭包捕获。


7. 指针

指针存的是内存地址。& 取地址,* 取值。

var a int = 10

fmt.Println(a) // 10,变量的值
fmt.Println(&a) // 0xc0000..,变量的地址

var p *int = &a // p 是指针,存的是 a 的地址
fmt.Println(p) // 0xc0000..,和 &a 一样
fmt.Println(*p) // 10,指针指向的值

*p = 20 // 通过指针修改值
fmt.Println(a) // 20,a 被改了

注意事项

不要操作未初始化的指针: 指针默认值是 nil,解引用会崩。

var p *int       // nil
// *p = 666 // panic!

var a int
p = &a // 先指向一个变量
*p = 666 // OK

不能取常量或字面量的地址:

const i = 5
// &i // 错误

// &10 // 错误

不支持指针运算: 和 C 不一样,Go 不能 p++p + 2

值传递 vs 指针传递

值传递复制一份,改不了原变量; 指针传递传地址,能改原变量。

// 值传递: 交换失败
func swapByValue(a, b int) {
a, b = b, a // 只改了副本
}

// 指针传递: 交换成功
func swapByPointer(a, b *int) {
*a, *b = *b, *a
}

func main() {
x, y := 10, 20

swapByValue(x, y)
fmt.Println(x, y) // 10 20,没变

swapByPointer(&x, &y)
fmt.Println(x, y) // 20 10,交换了
}

8. 数组

数组是固定长度、同一类型的数据集合。长度必须是常量,定义后不能改。

var arr [5]int              // 5 个 int,默认都是 0
fmt.Println(arr) // [0 0 0 0 0]
fmt.Println(len(arr)) // 5

arr[0] = 10 // 下标从 0 开始
fmt.Println(arr[0]) // 10

初始化

// 全部初始化
a := [5]int{1, 2, 3, 4, 5}

// 部分初始化,剩下的是零值
b := [5]int{1, 2, 3} // [1 2 3 0 0]

// 指定下标初始化
c := [5]int{2: 10, 4: 20} // [0 0 10 0 20]

// 让编译器数长度
d := [...]int{1, 2, 3} // 长度是 3

遍历

arr := [5]int{1, 2, 3, 4, 5}

// for 循环
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}

// range,更简洁
for i, v := range arr {
fmt.Printf("arr[%d] = %d\n", i, v)
}

// 只要值,不要下标
for _, v := range arr {
fmt.Println(v)
}

9. 切片

数组长度固定,切片长度可变。切片是引用类型。

// 数组: 长度固定,类型是 [5]int
var arr [5]int = [5]int{1, 2, 3, 4, 5}

// 切片: 长度可变,类型是 []int
var s []int = []int{1, 2, 3, 4, 5}

初始化

// 直接初始化
s := []int{1, 2, 3, 4}

// make(类型, 长度, 容量)
s1 := make([]int, 5, 10) // 长度 5,容量 10
s2 := make([]int, 5) // 长度和容量都是 5

len() 是当前元素个数,cap() 是底层数组能容纳的最大个数。

切片截取

切片可以从数组或另一个切片截取,语法是 s[low:high],包含 low 不包含 high

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

s[2] // 2,单个元素
s[:] // [0 1 2 3 4 5 6 7 8 9],全部
s[3:] // [3 4 5 6 7 8 9],从下标 3 到末尾
s[:5] // [0 1 2 3 4],从开头到下标 4
s[2:7] // [2 3 4 5 6],下标 2 到 6

还可以用第三个参数限制容量: s[low:high:max],容量是 max - low

s[2:5:8]  // [2 3 4],长度 3,容量 6

10. Map

map 是键值对的集合,无序。key 必须是可比较的类型 (支持 ==)。

// 声明
var m1 map[string]int // nil map,不能直接用

// make 创建
m2 := make(map[string]int) // 空 map,可以用
m3 := make(map[string]int, 10) // 指定初始容量

// 直接初始化
m4 := map[string]int{
"alice": 18,
"bob": 20,
}

增删改查

m := map[string]int{"alice": 18}

// 添加/修改
m["bob"] = 20 // 添加
m["alice"] = 19 // 修改

// 查询
age := m["alice"] // 19
age, ok := m["charlie"] // 0, false (不存在)

// 判断 key 是否存在
if val, ok := m["bob"]; ok {
fmt.Println(val)
}

// 删除
delete(m, "alice")

遍历

map 遍历顺序是随机的。

m := map[string]int{"a": 1, "b": 2, "c": 3}

for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}

// 只要 key
for k := range m {
fmt.Println(k)
}

引用类型

map 是引用类型,传参时不会复制,函数内修改会影响原 map

func addKey(m map[string]int) {
m["new"] = 100
}

func main() {
m := map[string]int{"a": 1}
addKey(m)
fmt.Println(m) // map[a:1 new:100]
}

11. 结构体

结构体是不同类型数据的集合。

type Student struct {
ID int
Name string
Age int
}

初始化

// 顺序初始化,必须全部字段
s1 := Student{1, "mike", 18}

// 指定字段,未指定的是零值
s2 := Student{Name: "bob", Age: 20}

// 先声明后赋值
var s3 Student
s3.ID = 1
s3.Name = "alice"

指针

// 取地址
p1 := &Student{1, "mike", 18}
p2 := &Student{Name: "bob"}

// new 创建
p3 := new(Student)
p3.ID = 1
p3.Name = "alice"

// 指针访问成员,p.Name 和 (*p).Name 等价
fmt.Println(p1.Name)

比较和赋值

同类型结构体可以用 == 比较,也可以直接赋值。

s1 := Student{1, "mike", 18}
s2 := Student{1, "mike", 18}
s3 := Student{2, "bob", 20}

fmt.Println(s1 == s2) // true
fmt.Println(s1 == s3) // false

s4 := s1 // 复制一份

值传递 vs 指针传递

// 值传递: 改不了原变量
func updateByValue(s Student) {
s.Age = 100
}

// 指针传递: 能改原变量
func updateByPointer(s *Student) {
s.Age = 100
}

func main() {
s := Student{1, "mike", 18}

updateByValue(s)
fmt.Println(s.Age) // 18,没变

updateByPointer(&s)
fmt.Println(s.Age) // 100,改了
}

可见性

首字母大写是公开的 (public),小写是私有的 (private,仅包内可见)。

type Student struct {
ID int // 公开
Name string // 公开
age int // 私有,其他包访问不了
}

12. 匿名组合

Go 没有传统的继承,但可以通过匿名字段实现类似效果。

type Person struct {
Name string
Age int
}

type Student struct {
Person // 匿名字段,继承 Person 的所有字段
ID int
Addr string
}

初始化

// 顺序初始化
s1 := Student{Person{"mike", 18}, 1, "beijing"}

// 指定字段
s2 := Student{
Person: Person{Name: "bob"},
ID: 2,
}

访问成员

匿名字段的成员可以直接访问,不用写完整路径。

s := Student{Person{"mike", 18}, 1, "beijing"}

// 直接访问
fmt.Println(s.Name) // mike
fmt.Println(s.Age) // 18

// 也可以写完整路径
fmt.Println(s.Person.Name)

// 修改
s.Name = "bob"
s.Person = Person{"alice", 20}

同名字段

如果外层和匿名字段有同名成员,就近原则。

type Student struct {
Person
Name string // 和 Person.Name 重名
}

func main() {
var s Student
s.Name = "student" // Student.Name
s.Person.Name = "person" // Person.Name
}

指针类型匿名字段

type Student struct {
*Person // 指针类型
ID int
}

func main() {
s1 := Student{&Person{"mike", 18}, 1}

// 或者用 new
var s2 Student
s2.Person = new(Person)
s2.Name = "bob"
}

13. 方法

方法是带接收者的函数,语法: func (receiver Type) methodName(params) returns

type Person struct {
Name string
Age int
}

// 值接收者
func (p Person) PrintInfo() {
fmt.Println(p.Name, p.Age)
}

// 指针接收者
func (p *Person) SetAge(age int) {
p.Age = age
}

func main() {
p := Person{"mike", 18}
p.PrintInfo() // mike 18
p.SetAge(20)
fmt.Println(p.Age) // 20
}

值接收者 vs 指针接收者

值接收者是拷贝,改不了原变量; 指针接收者是引用,能改原变量。

func (p Person) SetByValue(name string) {
p.Name = name // 改的是副本
}

func (p *Person) SetByPointer(name string) {
p.Name = name // 改的是原变量
}

func main() {
p := Person{"mike", 18}

p.SetByValue("bob")
fmt.Println(p.Name) // mike,没变

p.SetByPointer("bob")
fmt.Println(p.Name) // bob,变了
}

方法继承

匿名字段的方法会被继承。

type Person struct {
Name string
}

func (p Person) Say() {
fmt.Println("I'm", p.Name)
}

type Student struct {
Person
ID int
}

func main() {
s := Student{Person{"mike"}, 1}
s.Say() // I'm mike
}

方法重写

子类型可以定义同名方法覆盖父类型,就近原则。

func (s Student) Say() {
fmt.Println("Student:", s.Name)
}

func main() {
s := Student{Person{"mike"}, 1}
s.Say() // Student: mike
s.Person.Say() // I'm mike,显式调用
}

14. 接口

接口是方法签名的集合,定义行为不定义实现。实现了接口所有方法的类型就实现了该接口,不需要显式声明。

type Humaner interface {
SayHi()
}

实现接口

type Student struct {
Name string
}

func (s *Student) SayHi() {
fmt.Println("Student:", s.Name)
}

type Teacher struct {
Name string
}

func (t *Teacher) SayHi() {
fmt.Println("Teacher:", t.Name)
}

func main() {
var h Humaner

h = &Student{"mike"}
h.SayHi() // Student: mike

h = &Teacher{"bob"}
h.SayHi() // Teacher: bob
}

多态

同一个接口,不同类型有不同表现。

func WhoSayHi(h Humaner) {
h.SayHi()
}

func main() {
s := &Student{"mike"}
t := &Teacher{"bob"}

WhoSayHi(s) // Student: mike
WhoSayHi(t) // Teacher: bob

// 切片存不同类型
people := []Humaner{s, t}
for _, p := range people {
p.SayHi()
}
}

接口继承

接口可以嵌入其他接口。

type Humaner interface {
SayHi()
}

type Personer interface {
Humaner // 继承 Humaner
Sing(lrc string)
}

实现 Personer 必须同时实现 SayHi()Sing()

空接口

interface{} 可以存任意类型。

var i interface{}

i = 1
i = "hello"
i = &Student{"mike"}

类型断言

从空接口取出具体类型。

var i interface{} = "hello"

// if 判断
if s, ok := i.(string); ok {
fmt.Println("是 string:", s)
}

// switch 判断
switch v := i.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Println("其他类型")
}

15. Error 接口

errorGo 内建的接口,用于错误处理。

type error interface {
Error() string
}

创建 error

import (
"errors"
"fmt"
)

// errors.New
err1 := errors.New("something went wrong")

// fmt.Errorf,支持格式化
err2 := fmt.Errorf("user %d not found", 123)

错误处理模式

Go 的惯例是函数返回值最后一个是 error,调用后检查是否为 nil

func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}

func main() {
result, err := Divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("结果:", result)
}

标准模式:

result, err := someFunction()
if err != nil {
// 处理错误
return err
}
// 继续用 result

16. Panic

panic 是严重错误,触发后程序崩溃。

func main() {
fmt.Println("开始")
panic("出错了")
fmt.Println("不会执行")
}
// 输出:
// 开始
// panic: 出错了

触发方式

// 手动触发
panic("something went wrong")

// 运行时错误自动触发
var a [10]int
a[20] = 1 // 数组越界 -> panic

panic 与 defer

panic 之前注册的 defer 会执行,常用于清理资源。

func test() {
defer fmt.Println("清理工作")
panic("出错")
}
// 输出:
// 清理工作
// panic: 出错

17. Recover

recover 捕获 panic,防止程序崩溃。必须在 defer 中使用。

func test() {
defer func() {
if err := recover(); err != nil {
fmt.Println("捕获到:", err)
}
}()

panic("出错了")
}

func main() {
test()
fmt.Println("程序继续运行")
}
// 输出:
// 捕获到: 出错了
// 程序继续运行

不用 recoverpanic 会让整个程序崩溃; 用了 recover,程序可以继续执行。


18. Goroutine

go 关键字创建协程,非常轻量,可以轻松创建成千上万个。

func task() {
fmt.Println("任务执行")
}

func main() {
go task() // 创建协程,异步执行
time.Sleep(time.Second) // 等一下,否则 main 退出子协程也没了
}

主协程退出,所有子协程也会退出。

WaitGroup

sync.WaitGroup 等待所有协程完成。

var wg sync.WaitGroup

func task(id int) {
defer wg.Done()
fmt.Println("任务", id)
}

func main() {
for i := 0; i < 5; i++ {
wg.Add(1)
go task(i)
}
wg.Wait() // 等待全部完成
}

常用函数

runtime.Gosched()       // 让出 CPU,让其他协程先跑
runtime.Goexit() // 退出当前协程
runtime.GOMAXPROCS(n) // 设置使用的 CPU 核数

19. Channel

channel 用于 goroutine 之间通信,类似管道,一端发一端收,并发安全。

ch := make(chan int)      // 无缓冲
ch := make(chan int, 10) // 有缓冲,容量 10

ch <- 10 // 发送
x := <-ch // 接收

无缓冲 channel

发送方会阻塞,直到有接收方准备好。

ch := make(chan int)

go func() {
ch <- 10 // 阻塞,直到有人接收
}()

x := <-ch // 接收

有缓冲 channel

缓冲区没满就不阻塞,满了才阻塞。

ch := make(chan int, 3)

ch <- 1 // 不阻塞
ch <- 2 // 不阻塞
ch <- 3 // 不阻塞
ch <- 4 // 阻塞,满了

<-ch // 取走一个,腾出位置

关闭和遍历

ch := make(chan int)

go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 发完了,关闭
}()

for v := range ch { // 自动遍历直到关闭
fmt.Println(v)
}

关闭后不能再发送,但可以继续接收剩余数据。

单向 channel

限制只能发或只能收,用于函数参数约束。

func send(ch chan<- int) {  // 只能发
ch <- 10
}

func receive(ch <-chan int) { // 只能收
x := <-ch
}

定时器

// Timer: 一次性
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("时间到")

// Ticker: 周期性
ticker := time.NewTicker(time.Second)
for i := 0; i < 5; i++ {
<-ticker.C
fmt.Println("tick", i)
}
ticker.Stop()

20. Select

select 同时监听多个 channel,哪个准备好就执行哪个。

select {
case x := <-ch1:
fmt.Println("从 ch1 收到", x)
case ch2 <- y:
fmt.Println("发送到 ch2")
default:
fmt.Println("都没准备好")
}

基本用法

ch1 := make(chan int)
ch2 := make(chan int)

go func() {
time.Sleep(time.Second)
ch1 <- 1
}()

go func() {
time.Sleep(2 * time.Second)
ch2 <- 2
}()

select {
case x := <-ch1:
fmt.Println("ch1:", x) // 先到,执行这个
case x := <-ch2:
fmt.Println("ch2:", x)
}

超时处理

ch := make(chan int)

select {
case x := <-ch:
fmt.Println("收到:", x)
case <-time.After(3 * time.Second):
fmt.Println("超时")
}

斐波那契示例

func fibonacci(ch chan<- int, quit <-chan bool) {
x, y := 1, 1
for {
select {
case ch <- x:
x, y = y, x+y
case <-quit:
return
}
}
}

func main() {
ch := make(chan int)
quit := make(chan bool)

go func() {
for i := 0; i < 8; i++ {
fmt.Println(<-ch)
}
quit <- true
}()

fibonacci(ch, quit)
}

要点

  • 多个 case 同时准备好时随机选一个
  • default 在所有 case 阻塞时执行
  • 常用于超时控制和多路复用

21. 锁 (sync)

多个 goroutine 同时访问共享资源会有竞态问题,需要锁来保证安全。

互斥锁 sync.Mutex

同一时刻只有一个 goroutine 能访问。

var x = 0
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
defer wg.Done()
for i := 0; i < 5000; i++ {
lock.Lock()
x = x + 1
lock.Unlock()
}
}

func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x) // 10000
}

读写锁 sync.RWMutex

读多写少的场景用这个。多个读可以同时进行,写独占。

var rwlock sync.RWMutex

func write() {
rwlock.Lock() // 写锁
x = x + 1
rwlock.Unlock()
}

func read() {
rwlock.RLock() // 读锁
fmt.Println(x)
rwlock.RUnlock()
}

sync.Once

确保某操作只执行一次,常用于单例初始化。

var once sync.Once

func init() {
once.Do(func() {
fmt.Println("只执行一次")
})
}

sync.Map

并发安全的 map,不需要额外加锁。

var m sync.Map

m.Store("key", "value") // 存
value, ok := m.Load("key") // 取
m.Delete("key") // 删