深入探究 Go 中的并发性(不使用 Channels)
Go 的并发模型是其最突出的功能之一。虽然通道经常成为 goroutine 通信的焦点,但理解并发的核心,而不依赖通道,对于掌握 Go 至关重要。这篇文章深入探讨了 goroutine、`sync` 包和同步的实用模式。
并发与并行
并发是指同时管理多个任务,而并行是指同时执行多个任务。Go 专为并发而设计,因此可以轻松构建能够独立处理多个操作的程序。
Goroutines:构建块
**goroutine** 是 Go 运行时管理的轻量级线程。创建 goroutine 非常简单,只需在函数调用前加上“go”前缀即可。
package main import ( "fmt" "time" ) func task(name string) { for i := 0; i < 5; i++ { fmt.Printf("Task %s is running: %d\n", name, i) time.Sleep(time.Millisecond * 500) } } func main() { go task("A") go task("B") // Let the main function wait for goroutines to finish time.Sleep(time.Second * 3) fmt.Println("Main function exiting") }
无通道同步
使用 sync.WaitGroup
`sync.WaitGroup` 是一个强大的工具,可以等待多个 goroutine 完成其工作。
package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // Decrement the counter when the goroutine completes fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go worker(i, &wg) } wg.Wait() // Wait for all goroutines to finish fmt.Println("All workers completed") }
使用 sync.Mutex
当多个 goroutine 访问共享数据时,可能会发生竞争条件。`sync.Mutex` 可确保一次只有一个 goroutine 可以访问代码的关键部分。
package main import ( "fmt" "sync" ) type Counter struct { value int mu sync.Mutex } func (c *Counter) Increment() { c.mu.Lock() c.value++ c.mu.Unlock() } func main() { counter := &Counter{} var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Printf("Final Counter Value: %d\n", counter.value) }
实用模式
无通道的工作池
工作池是一种模式,多个工作器同时执行任务。工作器可以从受互斥锁保护的共享切片访问任务,而不是通过通道。
package main import ( "fmt" "sync" ) func worker(id int, tasks *[]int, mu *sync.Mutex, wg *sync.WaitGroup) { defer wg.Done() for { mu.Lock() if len(*tasks) == 0 { mu.Unlock() return } task := (*tasks)[0] *tasks = (*tasks)[1:] mu.Unlock() fmt.Printf("Worker %d processing task %d\n", id, task) } } func main() { tasks := []int{1, 2, 3, 4, 5} var mu sync.Mutex var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go worker(i, &tasks, &mu, &wg) } wg.Wait() fmt.Println("All tasks processed") }
并发陷阱
结论
即使没有通道,Go 中的并发功能也非常强大。通过掌握 goroutines、`sync` 包和常见模式,您可以编写高效、高性能的程序。试用这些工具,您很快就会明白为什么 Go 是并发编程的首选!