Go并发提炼
本小册子简要概述了Go中的许多并发主题。每个主题都有互动示例 —— 可以通过更改代码并点击运行来进行实验。还有一个包含静态示例的PDF版本。这是关于Go并发的快速回顾,而不是初学者指南。如果您想从头开始学习并发并进行实用练习,请查看我的另一本书 — 《Go的要点:并发》。本书不使用人工智能。 # Goroutines Go中的并发基础是goroutines —— 以go关键字启动的函数: ```go func main() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() fmt.Println("worker 1") }() go func() { defer wg.Done() fmt.Println("worker 2") }() wg.Wait() } ``` 输出: ``` worker 2 worker 1 ``` Go运行时管理这些goroutines,并将它们分配到在CPU内核上运行的操作系统线程上。与OS线程相比,goroutines是轻量级的,因此您可以创建数百或数千个。goroutines是完全独立的。主函数也是一个goroutine,但它在程序启动时隐式开始。当主函数结束时,其他goroutines也会关闭。在上面的示例中,我们使用一个等待组(sync.WaitGroup)来等待goroutines完成。等待组内部有一个计数器。调用Add(n)将其增加n,而Done()将其减少1。Wait()会阻塞调用的goroutine(在这种情况下是主函数),直到计数器达到零。这样,主函数在退出之前等待两个工作者完成。 WaitGroup.Go会自动增加等待组计数器,在一个goroutine中运行一个函数,并在完成时减少计数器: ```go func main() { var wg sync.WaitGroup wg.Go(func() { fmt.Println("worker 1") }) wg.Go(func() { fmt.Println("worker 2") }) wg.Wait() } ``` 输出: ``` worker 2 worker 1 ``` # Channels Goroutines可以通过channels相互传递值。channel就像一个窗口,一个goroutine可以在这里投掷某物,而另一个可以捕捉它: ```go func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) } ``` 输出: ``` ping ``` 通过channel发送值是一个同步操作。当发送的goroutine将一个值写入channel(ch <- val)时,它会阻塞并等待有人接收该值(<-ch)。只有这样,它才会继续。 ## 返回输出channel 从一个函数返回一个输出channel并在内部goroutine中填充它是Go中的一种常见模式。这允许调用者通过channel接收值,而拥有函数仍然控制它: ```go func generate(start, stop int) chan int { out := make(chan int) go func() { for i := start; i < stop; i++ { out <- i } }() return out } ``` ## 关闭channel 为了向读者发出信号,告知所有数据已发送,写入goroutine使用close()关闭channel: ```go func generate(start, stop int) chan int { out := make(chan int) go func() { defer close(out) for i := start; i < stop; i++ { out <- i } }() return out } ``` 阅读者在读取时使用第二个值("逗号OK")检查channel的状态: ```go func main() { in := generate(5, 10) for { num, ok := <-in if !ok { break } fmt.Print(num, " ") } } ``` 输出: ``` 5 6 7 8 9 ``` 当channel打开时,读取者接收下一个值和一个true状态。如果channel已关闭,读取者会得到一个零值和一个false状态。channel只能关闭一次。再次关闭或向关闭的channel写入会导致恐慌。关闭channel的唯一原因是向其读者发出信号,通知所有数据已发送。如果这对读者不重要,则不需要关闭它。当一个channel不再被使用时,Go的垃圾收集器会释放其资源,无论它是否关闭。 ## Channel迭代 range会自动读取channel中的下一个值并检查它是否已关闭。如果channel关闭,则退出循环: ```go func main() { nums := generate(5, 10) for n := range nums { fmt.Print(n, " ") } } ``` 输出: ``` 5 6 7 8 9 ``` 对channel的range返回单个值,而不是一对,笼统地说,跟对切片的范围不同。 ## 定向channels 您可以通过设置channel方向来保护自己免受意外的写入/关闭错误。channels可以是: - chan(双向):用于读取和写入(默认); - chan<-(仅发送):仅用于写入; - <-chan(仅接收):仅用于读取。 您不能从仅发送的channel读取,也不能写入仅接收的channel(也不能关闭它)。channels通常初始化为可双向读取和写入,并作为功能参数指定为定向。Go会自动将常规channel转换为定向channel: ```go stream := make(chan int) go func(in chan<- int) { in <- 42 }(stream) ```
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡