5.1 并发基础
5.1.1 并发和并行
5.1.2 goroutine
1) go的执行是非阻塞的,不会等待。
2) go后面函数的返回值会被忽略
3)调度器不能保证多个 goroutine 的执行次序
4) 没有父子goroutine的概念,所有的goroutine是平等地被调用和执行的。
5) Go程序在执行时会单独为 main 函数创建一个 gouroutine , 遇到其他go关键字时再去创建其他的goroutine
6) Go 没有暴露goroutine id 给用户,所以不能在一个goroutine里面显式地操作另一个goroutine,不过 runtime包提供了一些函数访问和设置goroutine的相关信息
1. func GOMAXPROCS
func GOMAXPROCS(n int) int 用来设置或查询可以并发执行的goroutine数目,n 大于 1表示设置gomaxprocs值,否则表示查询gomaxprocs值
import "runtime"
func main() {
// 获取当前GOMAXPROCS值
println("GOMAXPROCS=", runtime.GOMAXPROCS(0))
// 设置GOMAXPROCS值
runtime.GOMAXPROCS(2)
// 获取当前的GOMAXPROCS值
println("GOMAXPROCS=", runtime.GOMAXPROCS(0))
}
2. func Goexit
用于结束当前goroutine
3. func Gosched
放弃当前调度执行机会
5.1.3 chan
Go的哲学,不要通过共享内存来通信 ,而是通过通信来共享内存。chan通道是Go通过通信来共享内存的载体。
通道是有类型的: chan dataType
//创建一个无缓冲的通道,通道存放元素的类型为dataType
make(chan datatype)
//创建一个有10个缓存的通道,通道存放元素的类型为datatype
通道氛围无缓冲的通道和有缓冲的通道,Go提供内置函数len和cap,
无缓冲的通过的len和cap都是0,
有缓冲的通过 len 代表没有背读取的元素数,cap代表整个通道的容量
无缓存的通道既可以用于通信,也可以用于两个goroutine的同步,有缓冲的通道主要用于通信
package main
import(
"runtime"
)
func main() {
c := make(chan struct {})
go func(i chan struct {}) {
sum := 0
for i := 0; i < 10000; i++ {
sum += i
}
println(sum)
c <- struct {} {}
}(c)
// NumGoroutine可以返回当前程序的goroutine数目
println("NumGoroutine =", runtime.NumGoroutine())
//读通道c,通过通道就进行同步等待
<-c
}
缓冲通道和消息队列类似,有削峰和增大吞吐量的功能,例如:
package main
import (
"runtime"
)
func main() {
c := make(chan struct {})
ci := make(chan int, 1000000)
go func(i chan struct {}, j chan int) {
for i := 0; i < 1000000; i++ {
ci <- i
}
close(ci)
//写通道
c <- struct {} {}
}(c, ci)
// NumGoroutine 可以返回当前程序的goroutine数目
println("NumGoroutine =", runtime.NumGoroutine())
//读通道c, 通过通道进行同步等待
<-c
//此时ci通道已经关闭,匿名函数启动的goroutine已退出
println("NumGoroutine =", runtime.NumGoroutine())
//但通道ci还可以继续读取
for v := range ci {
println(v)
}
}
panic
1) 向已经关闭的通道写数据会导致panic
最佳实践是由写入者关闭通道,能最大程度地避免向已经关闭的通道写数据而导致的panic
2)重复关闭的通道会导致panic
阻塞
1) 向未初始化的通道写数据或者读数据都会导致当前goroutine的永久阻塞
2)向缓存区已满的通道写入数据会导致 goroutine 阻塞。
3) 通道中没有数据,读取该通道会导致 goroutine 阻塞
非阻塞
1) 读取已经关闭的通道不会引发阻塞,而是立即返回通道元素类型的零值,可以使用 comma , ok 语法判断通道是否关闭
2)向有缓冲且没有满的通道读/写不会引发阻塞
5.1.4 WaitGroup
type WaitGroup struct {
// contains filtered or unexported fields
}
// 添加等待信号
func (wg *WaitGroup) Add (delta int)
// 释放等待信号
func (wg *WaitGroup) Done()
//等待
func (wg *WaitGroup) Wait()
WaitGroup用来等待多个goroutine完成,main goroutine调用Add设置需要等待goroutine的数目,每一个goroutine结束时调用Done(),Wait()被main用来等待所有的goroutine完成
下面程序演示如何使用sync.WaitGroup完成多个goroutine之间的协同工作。
package main
import (
"net/http"
"sync"
)
var wg sync.WaitGroup
var urls = []string {
"https://www.baidu.com/",
"https://www.163.com/",
"https://www.qq.com",
}
func main() {
for _, url := range urls {
// 每一个url启动一个goroutine,同时给wg加1
wg.Add(1)
// Launch a goroutine to fetch the URL.
go func(url string) {
//当前goroutine结束后给wg计数减1, wg.Done()等价于wg.Add(-1)
// defer wq.Add(-1)
defer wg.Done()
//发送HTTP get请求并打印HTTP返回码
resp, err := http.Get(url)
if err == nil {
println(resp.Status)
}
}(url)
}
//等待所有请求结束
wg.Wait()
}
5.1.5 select
select 是类UNIX系统提供的一个多路复用系统API,Go语言借用多路复用的概念,提供了select关键字,用于多路监听多个通道。
当监听的通道没有状态是可读可写的,select是阻塞的;
只要监听的通道中有一个状态是可读或可写的,则 select 就不会阻塞,而是进入处理就绪通道的分支流程。
如果监听的通道有多个可读或可写的状态,则 select 随机选取一个处理。例如:
package main
func main() {
ch := make(chan int, 1)
go func(chan int) {
for {
select {
//0或1的写入是随机的
case ch <- 0:
case ch <- 1:
}
}
}(ch)
for i := 0; i < 10; i++ {
println(<-ch)
}
}
5.1.6 扇入(Fan in) 和 扇出 (Fan out)
扇入指将多路通道聚合到一条通道中处理,Go语言最简单的扇入就是使用select聚合多条通道服务;
扇出指将一条通道伐散到多条通道中处理,Go语言使用 go 关键字启动多个 goroutine 并发处理。
5.1.7 通知退出机制
读取已经关闭的通道不会引发阻塞,也不会导致panic,而是立即返回该通道存储类型的零值。
关闭select监听的某个通道能使select立刻感知这种通知,然后进行处理,这就是所谓的退出通知机制
5.2 并发范式
5.2.1 生成器
159
5.2.2 管道
通道可以分为两个方向,一个是读,一个是写
5.2.3 每个请求一个goroutine
5.2.4 固定 worker 工作池