Go 语言并发入门
Go 语言(Golang)由 Google 开发,充分考虑了现代性能和可扩展性需求。Go 的关键特性之一是其内置的 并发(concurrency)支持——即程序同时执行多个任务的能力。与传统的多线程(由操作系统管理线程)不同,Go 提供了轻量级的 goroutines 和便捷的同步原语——通道。
本文将介绍:
- 什么是 goroutines 以及如何启动它们
- 用于 goroutines 间数据交换的通道
- 用于安全访问共享资源的互斥锁
- 并发代码的实践示例和模式
1. Goroutines:Go 的轻量级线程
Goroutine 是一个与其他 goroutines 并发执行的函数。它仅占用几 KB 的栈空间(而 OS 线程的栈可能达到 MB 级别)。只需在函数调用前添加关键字 go 即可启动一个 goroutine。
示例:启动 goroutines
package main
import ( "fmt" "time")
func sayHello(name string) { for i := 0; i < 3; i++ { fmt.Printf("你好,%s! (迭代 %d)", name, i) time.Sleep(100 * time.Millisecond) }}
func main() { go sayHello("Alice") // 启动 goroutine go sayHello("Bob") // 再启动一个 goroutine time.Sleep(1 * time.Second) // 等待完成 fmt.Println("主函数执行完毕")}注意: 当 main 函数结束时,程序会终止,即使其他 goroutines 仍在运行。因此我们使用 time.Sleep 来等待。在实际代码中,最好使用 sync.WaitGroup。
使用 sync.WaitGroup
package main
import ( "fmt" "sync")
func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 完成时减少计数器 fmt.Printf("工作者 %d 开始工作", id) // ... 执行某些任务 ... fmt.Printf("工作者 %d 结束工作", id)}
func main() { var wg sync.WaitGroup
for i := 1; i <= 3; i++ { wg.Add(1) // 启动 goroutine 前增加计数器 go worker(i, &wg) }
wg.Wait() // 阻塞 main 直到所有 goroutines 完成 fmt.Println("所有工作者已完成工作")}2. 通道:goroutines 间的数据交换
通道(channels)是类型化的“管道”,goroutines 可以通过它发送和接收值。通道无需显式锁定即可实现安全的同步。
创建和使用通道
package main
import "fmt"
func main() { // 创建一个整数类型的通道 ch := make(chan int)
// 启动一个发送数据的 goroutine go func() { ch <- 42 // 向通道发送值 }()
// 从通道接收值 value := <-ch fmt.Println("接收到:", value)}默认情况下,通道是 无缓冲 的:发送会阻塞,直到接收方准备好接收数据,反之亦然。这确保了同步。
带缓冲的通道
package main
import "fmt"
func main() { // 创建一个容量为 3 的缓冲通道 ch := make(chan string, 3)
// 无阻塞发送(直到缓冲区满) ch <- "one" ch <- "two" ch <- "three"
// 读取数据 fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch)}关闭通道与 range 循环
package main
import "fmt"
func fibonacci(n int, ch chan int) { a, b := 0, 1 for i := 0; i < n; i++ { ch <-