Go并发模式:管道和取消

  • chan作为函数返回值的方式有3种:(chan int)、(<- chan int)、(chan <- int),分别代表(可读可写的管道)、(只读管道)、(只写管道),只读管道不能close(),只写管道可以close()

平方数

考虑具有三个阶段的管道。

第一阶段gen是一个函数,它将整数列表转换为一个通道,该通道发出列表中的整数。该gen函数启动一个 goroutine,在通道上发送整数,并在所有值都发送后关闭通道:

func gen(nums ...int) <-chan int {

out := make(chan int)

go func() {

for _, n := range nums {

out <- n

}

close(out)

}()

return out

}
复制代码

第二阶段,sq从通道接收整数并返回一个通道,该通道发出每个接收到的整数的平方。在入站通道关闭并且此阶段已将所有值发送到下游后,它将关闭出站通道:

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
    for n := range in {
    out <- n * n
}
    close(out)
}()
    return out

}
复制代码

该main函数设置管道并运行最后阶段:它从第二阶段接收值并打印每个值,直到通道关闭:

func main() {
// 设置管道。
c := gen(2, 3)
out := sq(c) // 消费输出。
fmt.Println(<-out) // 4
fmt.Println(<-out) // 9
}

复制代码

由于sq其入站和出站通道具有相同的类型,因此我们可以将其组合任意次。我们也可以main像其他阶段一样重写为范围循环:


// 设置管道并消耗输出。
for n := range sq(sq(gen(2, 3))) {
fmt.Println(n) // 16 然后 81
}
} 
复制代码

扇出,扇入

多个函数可以从同一个通道读取,直到该通道关闭;这称为扇出。这提供了一种在一组工作人员之间分配工作以并行化 CPU 使用和 I/O 的方法。

一个函数可以从多个输入中读取并继续进行,直到所有输入都关闭,方法是将输入通道多路复用到一个通道上,当所有输入都关闭时,该通道才关闭。这称为扇入。

我们可以更改管道以运行 的两个实例sq,每个实例都从同一输入通道读取。我们引入了一个新函数merge来扇入结果:

func main() {
in := gen(2, 3) // 将 sq 工作分配到两个都从 in 读取的 goroutine 上。
c1 := sq(in)
c2 := sq(in) // 使用合并后的输出c1 和 c2。
for n := range merge(c1, c2) {
fmt.Println(n) // 4 then 9, or 9 then 4
}
}
复制代码

该merge函数通过为每个入站通道启动一个 goroutine,将值复制到唯一的出站通道,从而将通道列表转换为单个通道。一旦所有的outputgoroutine 都启动了,merge在该通道上的所有发送完成后,再启动一个 goroutine 来关闭出站通道。

在关闭的通道上发送 panic,所以在调用 close 之前确保所有发送都完成很重要。该 sync.WaitGroup类型提供了一种安排这种同步的简单方法:

func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup

out := make(chan int) // 为 cs 中的每个输入通道启动一个输出 goroutine。output // 将值从 c 复制到 out 直到 c 关闭,然后调用 wg.Done。

output := func(c <-chan int) {
for n := range c {
out <- n
} wg.Done()

} wg.Add(len(cs))
for _, c := range cs {
go output (c)

} // 一旦所有输出 goroutines完成,启动一个 goroutine 关闭。这必须在 wg.Add 调用之后开始。
go func() { wg.Wait()
close(out)
}()
return
} 
复制代码

工程化

v1

  • 失血模型   像面对对象一样在service层里做一堆逻辑判断
  • 贫血模型   把一些逻辑包含在model里
  • 一般是三层模型+贫血模型 

v2

  • IOC 控制反转 对象初始化在外部处理,单次初始化和复用方便单元测试

Go的并发模型

 Go运行时系统通过构造G-P-M对象模型实现了一套用户态的并发调度系统,可以自己管理和调度自己的并发任务,所以可以说Go语言原生支持并发。自己实现的调度器负责将并发任务分配到不同的内核线程上运行,然后内核调度器接管内核线程在CPU上的执行与调度。

可以看到Go的并发用起来非常简单,用了一个语法糖将内部复杂的实现结结实实的包装了起来。其内部可以用下面这张图来概述:

Image.png