Go语言精进之路读书笔记第31条——优先考虑并发设计
31.1 并发与并行
1.并行方案
在处理器核数充足的情况下启动多个单线程应用的实例
2.并发方案
重新做应用结构设计,即将应用分解成多个在基本执行单元(例如操作系统线程)中执行的、可能有一定关联关系的代码片段
goroutine:由Go运行时负责调度的用户层轻量级线程,相比传统操作系统线程而言具有如下优势
(1) 资源占用小,每个goroutine的初始栈大小仅为2KB,参考$GOROOT/src/runtime/stack.go::_StackMin
(2) 由Go运行时而不是操作系统调度,goroutine上下文切换代价较小
(3) 语言原生支持:goroutine由go关键字接函数或方法创建,函数或方法返回即表示goroutine退出
(4) 语言内置channel作为goroutine间通信原语,为并发设计提供强大支撑
31.2 Go并发设计实例
1.第一版:顺序设计
func idCheck() int {
time.Sleep(time.Millisecond * time.Duration(idCheckTmCost))
println("\tidCheck ok")
return idCheckTmCost
}
func bodyCheck() int {
time.Sleep(time.Millisecond * time.Duration(bodyCheckTmCost))
println("\tbodyCheck ok")
return bodyCheckTmCost
}
func xRayCheck() int {
time.Sleep(time.Millisecond * time.Duration(xRayCheckTmCost))
println("\txRayCheck ok")
return xRayCheckTmCost
}
func airportSecurityCheck() int {
println("airportSecurityCheck ...")
total := 0
total += idCheck()
total += bodyCheck()
total += xRayCheck()
println("airportSecurityCheck ok")
return total
}
func main() {
total := 0
passengers := 30
for i := 0; i < passengers; i++ {
total += airportSecurityCheck()
}
println("total time cost:", total)
}
30名乘客,只设置了一条安检通道,共耗时10800ms
2.第二版:并行方案
func idCheck(id int) int {
time.Sleep(time.Millisecond * time.Duration(idCheckTmCost))
print("\tgoroutine-", id, ": idCheck ok\n")
return idCheckTmCost
}
func bodyCheck(id int) int {
time.Sleep(time.Millisecond * time.Duration(bodyCheckTmCost))
print("\tgoroutine-", id, ": bodyCheck ok\n")
return bodyCheckTmCost
}
func xRayCheck(id int) int {
time.Sleep(time.Millisecond * time.Duration(xRayCheckTmCost))
print("\tgoroutine-", id, ": xRayCheck ok\n")
return xRayCheckTmCost
}
func airportSecurityCheck(id int) int {
print("goroutine-", id, ": airportSecurityCheck ...\n")
total := 0
total += idCheck(id)
total += bodyCheck(id)
total += xRayCheck(id)
print("goroutine-", id, ": airportSecurityCheck ok\n")
return total
}
func start(id int, f func(int) int, queue <-chan struct{}) <-chan int {
c := make(chan int)
go func() {
total := 0
for {
_, ok := <-queue
if !ok {
c <- total
return
}
total += f(id)
}
}()
return c
}
func max(args ...int) int {
n := 0
for _, v := range args {
if v > n {
n = v
}
}
return n
}
func main() {
total := 0
passengers := 30
c := make(chan struct{})
c1 := start(1, airportSecurityCheck, c)
c2 := start(2, airportSecurityCheck, c)
c3 := start(3, airportSecurityCheck, c)
for i := 0; i < passengers; i++ {
c <- struct{}{}
}
close(c)
total = max(<-c1, <-c2, <-c3)
println("total time cost:", total)
}
30名乘客,设置了三条安检通道,共耗时3600ms
3.第三版:并发方案
func idCheck(id string) int {
time.Sleep(time.Millisecond * time.Duration(idCheckTmCost))
print("\tgoroutine-", id, "-idCheck: idCheck ok\n")
return idCheckTmCost
}
func bodyCheck(id string) int {
time.Sleep(time.Millisecond * time.Duration(bodyCheckTmCost))
print("\tgoroutine-", id, "-bodyCheck: bodyCheck ok\n")
return bodyCheckTmCost
}
func xRayCheck(id string) int {
time.Sleep(time.Millisecond * time.Duration(xRayCheckTmCost))
print("\tgoroutine-", id, "-xRayCheck: xRayCheck ok\n")
return xRayCheckTmCost
}
func start(id string, f func(string) int, next chan<- struct{}) (chan<- struct{}, chan<- struct{}, <-chan int) {
queue := make(chan struct{}, 10)
quit := make(chan struct{})
result := make(chan int)
go func() {
total := 0
for {
select {
case <-quit:
result <- total
return
case v := <-queue:
total += f(id)
if next != nil {
next <- v
}
}
}
}()
return queue, quit, result
}
func newAirportSecurityCheckChannel(id string, queue <-chan struct{}) {
go func(id string) {
print("goroutine-", id, ": airportSecurityCheckChannel is ready...\n")
// start xRayCheck routine
queue3, quit3, result3 := start(id, xRayCheck, nil)
// start bodyCheck routine
queue2, quit2, result2 := start(id, bodyCheck, queue3)
// start idCheck routine
queue1, quit1, result1 := start(id, idCheck, queue2)
for {
select {
case v, ok := <-queue:
if !ok {
close(quit1)
close(quit2)
close(quit3)
total := max(<-result1, <-result2, <-result3)
print("goroutine-", id, ": airportSecurityCheckChannel time cost:", total, "\n")
print("goroutine-", id, ": airportSecurityCheckChannel closed\n")
return
}
queue1 <- v
}
}
}(id)
}
func max(args ...int) int {
n := 0
for _, v := range args {
if v > n {
n = v
}
}
return n
}
func main() {
passengers := 30
queue := make(chan struct{}, 30)
newAirportSecurityCheckChannel("channel1", queue)
newAirportSecurityCheckChannel("channel2", queue)
newAirportSecurityCheckChannel("channel3", queue)
time.Sleep(5 * time.Second) // 保证上述三个goroutine都已经处于ready状态
for i := 0; i < passengers; i++ {
queue <- struct{}{}
}
time.Sleep(5 * time.Second)
close(queue) // 为了打印各通道的处理时长
time.Sleep(1000 * time.Second)
}
30名乘客,还是三条安检通道,但是增加了工作人员(计算资源),共耗时2160ms