- 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的并发用起来非常简单,用了一个语法糖将内部复杂的实现结结实实的包装了起来。其内部可以用下面这张图来概述:
近期评论