这是我参与[第五届青训营]笔记创作活动的第一天。
简介
本笔记主要是记录关于go并发编程的四个关键知识,包括 goroutine, channel, lock, WaitGroup。
goroutine
goroutine可以认为是go语言的协程,它运行在一个或多个线程中,是go高并发执行的基础。
使用go关键字来创建一个goroutine。
go func() {
// func body
}()
channel
channel又名通道,是Go语言中不同goroutine通信的管道,其基本用于共享一块内存。
其基本操作为:
- 创建
channel的类型:
- 可取可存
chan T // 可以接收和发送类型为T的数据
- 只能发数据
chan<- T // 只能发送类型为T的数据
- 只能收数据
<-chan T // 只能接收类型为T的数据
通过make以及chan关键字生成一个通道, 当缓冲大小缺失时,为无缓冲通道,否则为对应缓冲大小的通道。
ch := make(chan 元素类型,[缓冲大小])
src := make(chan int)
dest := make(chan int, 3)
- 遍历
利用range可以使用遍历通道中的元素
for i := range src
- 存取操作 利用<-操作符,当 channel <- data 为发送数据到通道中,当 data <- channel 为从通道中取数。
channel <- v
v := <- channel
lock
lock 为 go 语言中的锁,利用其可以实现对于共享内存中的正确访问。假设现在 go 程序中有两个 goroutine 在同时运作,那么他们都需要对于一个相同的元素 x = 0 先取值再递增,此时第一个 goroutine 正在运行,当它完成取数操作时,就进行协程切换,此时第二个 goroutine 运行,它也取到了 x, 那么此时 x 的值为 1, 而不为 2,所以这正是我们对于共享内容中需要上锁的原因。
lock 有上锁操作和取消上锁操作。
以下是一个简单的 go lock 的程序
var (
x int64
lock sync.Mutex // 定义互斥锁
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock() // 上锁
x += 1
lock.Unlock() // 取消上锁
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func main() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock", x) // 9137
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock", x) // 10000
}
WaitGroup
使用 WaitGroup 的原因是 main(或者其它一些函数) 其实是一个主任务,有时需要保证多个并发任务完成时,再继续执行主任务,所以需要先使主任务暂停以等待并发任务的完成。
WaitGroup 一共有三个方法:
Add方法用于设置 WaitGroup 的计数值,可以理解为子任务的数量Done方法用于将 WaitGroup 的计数值减一,可以理解为完成一个子任务Wait方法用于阻塞调用者,直到 WaitGroup 的计数值为0,即所有子任务都完成
使用实例
func hello(i int) {
println("goroutine : ", i)
}
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
以上仅为个人理解,如有错误,烦请指正。