这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
Go语言并发编程
1、Goroutine(Go语言是如何实现并发的)
前置知识:
- 并行与并发
- 进程与线程
- Go语言通过协程实现并发
- 多个协程之间是并发执行的
- 一个线程上可以跑很多个协程
2、创建协程
在被调用的函数前面加 go 关键字,表示这是一个协程 下边代码展示了利用协程打印0~4
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 5; i++ {
go hello(i)
}
time.Sleep(time.Second) //让主函数睡一秒,否则主函数结束后,其中包含的协程也会自动停止
}
func hello(i int) {
println("hello:" + fmt.Sprint(i))
}
运行结果:
3、通信和共享内存
官方建议通过通信实现共享内存,而不是通过共享内存实现通信
如何理解呢?
通信实现共享内存即:协程之间通过管道传递信息,以此实现多个协程共享某些变量或资源,只有协程1将变量a传递给协程2,才允许协程2访问变量a
共享内存实现通信即:多个协程可以共同访问某个全局变量,通过全局变量实现协程之间的 通信
Go语言对上述俩种方式均支持,但更推荐前者
4、channel(通信实现共享内存)
通过make创建一个管道,需要指定管道类型,大小可选(不写即大小为1,也就是不带缓冲区的管道)。 协程之间通过管道通信
一个简单的生产者消费者模型
package main
import (
"fmt"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int, 3)
//生产者
go func() {
defer close(chan1)
for i := 0; i < 10; i++ {
chan1 <- i
}
}()
//消费者
go func() {
defer close(chan2)
for i := range chan1 {
chan2 <- i * i
}
}()
//遍历chan2
for i := range chan2 {
fmt.Println(i)
}
}
关于defer的用法参考:深入了解golang中的defer关键字 - 掘金 (juejin.cn)
5、Lock(共享内存实现通信)
通过对全局变量加锁,实现多个协程互斥访问变量x
package main
import (
"fmt"
"sync"
"time"
)
var (
x int
lock sync.Mutex
)
func main() {
for i := 0; i < 10; i++ {
go addWithLock()
}
time.Sleep(time.Second) //保证10个协程跑完
fmt.Println(x) //输出10000
x = 0
for i := 0; i < 10; i++ {
go addWithOutLock()
}
time.Sleep(time.Second)
fmt.Println(x) //小于10000
}
//加锁
func addWithLock() {
for i := 0; i < 1000; i++ {
lock.Lock()
x++
lock.Unlock()
}
}
//不加锁
func addWithOutLock() {
for i := 0; i < 1000; i++ {
x++
}
}
6、WaitGroup
通过三个基本操作控制协程:
- Add(delta int) 计数器+delta
- Done() 计数器-1
- Wait() 阻塞(一直到计数器为0)
实例:
package main
import (
"fmt"
"sync"
)
var (
wg sync.WaitGroup
)
func main() {
wg.Add(5) //计数器加5
for i := 0; i < 5; i++ {
go hello(i)
}
wg.Wait() //阻塞当前协程,直到计数器为0
}
func hello(i int) {
println("hello:" + fmt.Sprint(i))
wg.Done() //计数器减1
}