这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天。
线程与协程
- 线程:内核态,线程可以跑多个协程,栈
MB级别(1-8MB) - 协程:用户态,轻量级线程,栈
KB级别(2KB)
在 GO 中,可以直接在函数体调用前面加入关键字 go,即可为其创建一个协程( goroutine )来运行
值得注意的是:
- 当主函数运行完,但仍有协程在运行时,程序也会直接结束
- 因为不同协程在同一段时间内是并发/并行,因此他的运行顺序不一定是按照创建协程的顺序进行
协程间通信
Go语言提倡 通过通信共享内存 而不是 通过共享内存实现通信
协程可以通过 channel 来通信
channel 可以通过管道来创建 make(chan type, num)
type是数据类型num是管道的缓冲长度,如果不填写该参数,则无缓冲长度
锁
不同协程在访问相同内存的时候会存在问题,因为协程是并发/并行的,当多个读写操作同时进行时(至少有一个是写操作),赋值就会出现问题
进一步解释:假设此时有 A1 和 A2 两个协程,如果都对相同内存 cnt 进行赋值 + 1 操作,可能出现如下情况:
cnt = 0
A1 读取 cnt = 0
A2 读取 cnt = 0
A1 写入 cnt = 1
A2 写入 cnt = 1
如下代码中,运行后发现答案不一定为 100000
package main
import (
"fmt"
"time"
)
var cnt int = 0
func calc(n int) {
for i := 0; i < n; i++ {
cnt += 1
}
}
func main() {
for i := 0; i < 100; i++ {
go calc(1000)
}
time.Sleep(2 * time.Second)
fmt.Println(cnt)
}
为了解决上述问题,引入了锁的概念,保证在任意一个时刻,处理相同内存的至多只有一个协程
var mx sync.Mutex
func calc(n int) {
mx.Lock()
defer mx.Unlock()
for i := 0; i < n; i++ {
cnt += 1
}
}
在上述内容中,曾提及“当主函数运行完,但仍有协程在运行时,程序也会直接结束”,因此考虑三种方法来解决该问题
- 如上述代码一般,直接让主函数挂起一定时间,保证其他
goroutine能够运行完 - 使用
channel返回信号,这里适用于知道有多少个goroutine
package main
import (
"fmt"
"sync"
)
var cnt int = 0
var mx sync.Mutex
func calc(c chan int) {
mx.Lock()
for i := 0; i < 100; i++ {
cnt += 1
}
mx.Unlock()
c <- 1
}
func main() {
c := make(chan int)
for i := 0; i < 1000; i++ {
go calc(c)
}
for i := 0; i < 1000; i++ {
<-c
}
fmt.Println(cnt)
}
- 使用
sync.WaitGroup
当进入一个 goroutine 的时候,进行 Add(1),当其退出的时候,运行 Done();在主函数用 Wait() 等待
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var cnt int = 0
var mx sync.Mutex
var wg sync.WaitGroup
func calc() {
wg.Add(1)
defer wg.Done()
mx.Lock()
for i := 0; i < 100; i++ {
cnt += 1
}
mx.Unlock()
}
func main() {
rand.Seed(time.Now().Unix())
for i := 0; i < rand.Intn(1000); i++ {
go calc()
}
wg.Wait()
fmt.Println(cnt)
}
依赖管理
go model 是 Go 最新的用来控制版本的方案,主要是处理项目中引入了多个不同版本的相同库,则需要通过 go model 来进行管理
下面是简单的使用命令总结:
初始化:go mod init [address],使用后会出现一个 go.mod 文件
下载库:go get -u url,使用后会将 url 对应的库包下载到 GOPATH/pkg/mod 里面,并且产生一个 go.sum 用来记录库的版本信息以及哈希值
清理库:go mod tidy,使用后会将未使用的库从 go.sum 中删除,并且自动引入使用的库