Golang 学习笔记
基础
语法
只记录需要特别注意的点
Diff
- 变量声明未使用会 CE。
_ 的妙用
交换变量:a, b = b, a
空白标识符 _ 用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值
goto 的妙用
对于收到程序错误,打印信息的重复代码可以用 goto 来简化代码。跳出多重循环不推荐。
err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
fmt.Println("done")
return
onExit:
fmt.Println(err)
exitProcess()
函数妙用
import (
"fmt"
"math"
)
func main(){
/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
/* 使用函数 */
fmt.Println(getSquareRoot(9))
}
defer 语句
延迟执行,在函数执行结束后进行。
并且按照语句声明顺序,先进后出(类似栈)
map,无序集合
查看元素是否存在
/* 使用 key 输出 map 值 */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}
/* 查看元素在集合中是否存在 */
captial, ok := countryCapitalMap["United States"]
/* 如果 ok 是 true, 则存在,否则不存在 */
if (ok) {
fmt.Println("Capital of United States is", captial)
} else {
fmt.Println("Capital of United States is not present")
}
删除元素
delete(mp, "key")
错误处理
error 类型是一个借口类型,定义:
type error interface {
Error() string
}
反射 Reflect
在不知道具体类型的情况下,可以用反射来更新变量值,查看变量类型
Typeof 返回类型
Valueof 返回值
通过反射修改值,需要传入变量地址修改
类的方法
Go 中没有类的概念,但可以为结构体定义方法。
定义某些方法的时候,需要声明接受者(类名和替代的名字),声明在 func 关键字与函数名之间。
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) { // 接受者类型是一个指针,没有返回值的方法
v.X = v.X * f
v.Y = v.Y * f
}
当接受者类型是一个指针的时候。
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
这样的代码,v 的成员值也会发生变化,如果是 Scale(v, 5)
会发生编译错误,所以定义成指针类型接受者就变得很常见了
接受者类型为值的方法也是一样的道理。
一些好用的包中的函数
strings
strings.Filed(s string)
以空格分割字符串返回一个 string 数组。
Go 的并发
goroutine
动态栈
操作系统的线程一般都有固定的栈内存(通常为2MB),而 Go 语言中的 goroutine 非常轻量级,一个 goroutine 的初始栈空间很小(一般为2KB),所以在 Go 语言中一次创建数万个 goroutine 也是可能的。并且 goroutine 的栈不是固定的,可以根据需要动态地增大或缩小, Go 的 runtime 会自动为 goroutine 分配合适的栈空间。
调度器采用 GPM 调度模型
- G: 表示goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等;另外G对象是可以重用的。
- P: 表示逻辑processor,P的数量决定了系统内最大可并行的G的数量(前提:系统的物理cpu核数>=P的数量);P的最大作用还是其拥有的各种G对象队列、链表、一些cache和状态。
- M: M代表着真正的执行计算资源。在绑定有效的p后,进入schedule循环;而schedule循环的机制大致是从各种队列、p的本地队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到m,如此反复。M并不保留G状态,这是G可以跨M调度的基础。
创建
在函数或方法前声明 go
关键字
同步
需要创建 sync.WaitGroup 类型变量
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func hello() {
defer wg.Done() // 计数器 - 1
fmt.Println("hello")
}
func main() {
wg.Add(1) // 计数器 + 1
go hello()
fmt.Println("END!")
wg.Wait()
}
channel 通道类型
Go语言采用的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存,而不是通过共享内存而实现通信
通道创立与关闭
声明一个, var 变量名 chan 元素类型
需要初始化否则默认为零值,这样声明是一个没有缓冲的通道(必须定义接收方的接受操作否则死锁)
注意:无缓冲不等于缓冲大小为 1。
初始化:make(chan 元素类型,[缓冲大小])
close(通道名)
关闭通道,关闭后的通道有以下特点:
- 对一个关闭的通道再发送值就会导致 panic。
- 对一个关闭的通道进行接收会一直获取值直到通道为空。
- 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
- 关闭一个已经关闭的通道会导致 panic。
单向通道
以上都是双向通道,对于单向通道的声明:
<- chan int
只能接收chan <- int
只能发送
select 多路复用
- 可处理一个或多个channel的发送/接收操作
- 如果多个case同时满足,select会随机选择一个执行
- 对于没有case的select会一直阻塞,可用于阻塞 main 函数,防止退出
并发安全和互斥锁
互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个 goroutine 可以访问共享资源。Go语言中使用sync包中提供的Mutex类型来实现互斥锁
import (
"fmt"
"sync"
)
var (
x int64
wg sync.WaitGroup
m sync.Mutex // 互斥锁
)
func add() {
for i := 0; i < 5000; i++ {
m.Lock() // 修改x前加锁
x = x + 1
m.Unlock() // 改完解锁
}
wg.Done()
}
读写互斥锁
大多数场景读多写少
读写锁分为两种 读锁和写锁:
- 当一个 goroutine 获取到读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁。
- 如果是获取写锁就会等待;而当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待。
import (
"fmt"
"sync"
"time"
)
var (
x = 0
wg sync.WaitGroup
// lock sync.Mutex
rwlock sync.RWMutex
)
// rwlock.RLock() 锁上读锁
// rwlock.RUnlock() 读锁解除
// rwlock.Lock() 写锁锁上
// rwlock.UnLock() 解除写锁