分享 Golang 并发基础库,扩展以及三方库的一些常见问题、使用介绍和技巧,以及对一些并发库的选择和优化探讨。
go 原生/扩展库
提倡的原则
不要通过共享内存进行通信;相反,通过通信来共享内存。
Goroutine
goroutine 并发模型
调度器主要结构
主要调度器结构是 M,P,G
- M,内核级别线程,goroutine 基于 M 之上,代表执行者,底层线程,物理线程
- P,处理器,用来执行 goroutine,因此维护了一个 goroutine 队列,里面存储了所有要执行的 goroutine,将等待执行的 G 与 M 对接,它的数目也代表了真正的并发度( 即有多少个 goroutine 可以同时进行 );
- G,goroutine 实现的核心结构,相当于轻量级线程,里面包含了 goroutine 需要的栈,程序计数器,以及所在 M 的信息
P 的数量由环境变量中的 GOMAXPROCS 决定,通常来说和核心数对应。
映射关系
用户空间线程和内核空间线程映射关系有如下三种:
- N:1
- 1:1
- M:N
调度图
关系如图,灰色的 G 则是暂时还未运行的,处于就绪态,等待被调度,这个队列被 P 维护
注: 简单调度图如上,有关于 P 再多个 M 中切换,公共 goroutine 队列,M 从线程缓存中创建等步骤没有体现,复杂过程可以参考文章简单了解 goroutine 如何实现。
goroutine 使用
- demo1
go list.Sort()
- demo2
funcAnnounce(message string, delay time.Duration) {
gofunc() {
time.Sleep(delay)
fmt.println(message)
}()
}
channel
channel 特性
创建
// 创建 channel
a := make(chan int)
b := make(chan int, 10)
// 单向 channel
c := make(chan<- int)
d := make(<-chan int)
存入/读取/关闭
tip:
v, ok := <-a // 检查是否成功关闭(ok = false:已关闭)
channel 使用/基础
use channel
ci := make(chan int)
cj := make(chan int, 0)
cs := make(chan *os.File, 100)
c := make(chan int)
go func() {
list.Sort()
c <- 1
}()
doSomethingForValue
<- c
func Server(queue chan *Request) {
for req := range queue {
sem <- 1
go func() {
process(req)
<- sem
}()
}
}
func Server(queue chan *Requet) {
for req := range queue {
sem <- 1
go func(req *Request) {
process(req)
<- sem
}(req)
}
}
func Serve(queue chan *Request) {
for req := range queue {
req := req
sem <- 1
go func() {
process(req)
<-sem
}()
}
}
channel 使用/技巧
等待一个事件,也可以通过 close 一个 channel 就足够了。。
c := make(chan bool)
go func() {
// close 的 channel 会读到一个零值
close(c)
}()
<-c
阻塞程序
开源项目【是一个支持集群的 im 及实时推送服务】里面的基准测试的案例
资料领取直通车:Golang云原生最新资料+视频学习路线https://docs.qq.com/doc/DTllySENWZWljdWp4
Go语言学习地址:Golang DevOps项目实战https://ke.qq.com/course/422970?flowToken=1043212
取最快结果
func main() {
ret := make(chan string, 3)
for i := 0; i < cap(ret); i++ {
go call(ret)
}
fmt.Println(<-ret)
}
func call(ret chan<- string) {
// do something
// ...
ret <- "result"
}
协同多个 goroutines
注: 协同多个 goroutines 方案很多,这里只展示 channel 的一种。
limits := make(chan struct{}, 2)
for i := 0; i < 10; i++ {
go func() {
// 缓冲区满了就会阻塞在这
limits <- struct{}{}
do()
<-limits
}()
}
搭配 select 操作
for {
select {
case a := <- testChanA:
// todo a
case b, ok := testChanB:
// todo b, 通过 ok 判断 tesChanB 的关闭情况
default:
// 默认分支
}
}
main go routinue 确认 worker goroutinue 真正退出的方式
func worker(testChan chan bool) {
for {
select {
// todo some
// case ...
case <- testChan:
testChan <- true
return
}
}
}
func main() {
testChan := make(chan bool)
go worker(testChan)
testChan <- true
<- testChan
}
关闭的 channel 不会被阻塞
testChan := make(chan bool)
close(testChan)
zeroValue := <- testChan
fmt.Println(zeroValue) // false
testChan <- true // panic: send on closed channel
注: 如果是 buffered channel
, 即使被 close, 也可以读到之前存入的值,读取完毕后开始读零值,写入则