Go语言作为现代编程语言,其并发编程的优势是有目共睹的。在实际编程中,我们常常需要保证多个goroutine之间的同步,这就需要使用到Go语言的sync标准库。

sync库提供了基本的同步原语,例如互斥锁(Mutex)和等待组(WaitGroup),这些都是协调和控制并发执行的重要工具。

基础应用

1. 使用Mutex实现互斥

在很多情况下,我们需要保证在任意时刻只有一个goroutine能够访问某个数据。这时我们就可以使用Mutex(互斥锁)来实现这个需求。Mutex有两个方法:LockUnlock

LockUnlock之间的代码块,同一时刻只有一个goroutine可以执行,其他尝试执行这部分代码的goroutine会被阻塞直到锁被解开。以下面的例子为例,我们试图在多个goroutine中对一个全局变量完成加法操作:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. var (
  7. sum int
  8. mu sync.Mutex
  9. )
  10. func worker() {
  11. for i := 0; i < 10; i++ {
  12. mu.Lock()
  13. sum = sum + 1
  14. mu.Unlock()
  15. }
  16. }
  17. func main() {
  18. var wg sync.WaitGroup
  19. for i := 0; i < 10000; i++ {
  20. wg.Add(1)
  21. go func() {
  22. defer wg.Done()
  23. worker()
  24. }()
  25. }
  26. wg.Wait()
  27. fmt.Println(sum)
  28. }

2. 使用WaitGroup等待并发操作结束

在另外一种常见的应用场景中,我们需要开启一组goroutine去处理任务,而主goroutine需要等待这些任务完成后才能结束。这可以通过sync.WaitGroup来实现。

WaitGroup有三个方法:Add增加计数,Done减少计数,Wait等待计数归零。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. func worker(id int, wg *sync.WaitGroup) {
  8. defer wg.Done()
  9. fmt.Printf("Worker %d starting\n", id)
  10. time.Sleep(time.Second)
  11. fmt.Printf("Worker %d done\n", id)
  12. }
  13. func main() {
  14. var wg sync.WaitGroup
  15. for i := 1; i <= 5; i++ {
  16. wg.Add(1)
  17. go worker(i, &wg)
  18. }
  19. wg.Wait()
  20. }

以上的代码中,我们创建了5个worker goroutine,main goroutine 会等待所有的worker都完成工作后才会退出。

进阶应用

以下是一些稍微复杂一些的sync库的使用例子。

全局单例

使用sync.Once来保证某个操作只执行一次。这在初始化全局变量或者单例模式中非常有用。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. var once sync.Once
  7. func setup() {
  8. fmt.Println("Init function")
  9. }
  10. func worker(wg *sync.WaitGroup, n int) {
  11. once.Do(setup)
  12. fmt.Println("Worker", n)
  13. wg.Done()
  14. }
  15. func main() {
  16. var wg sync.WaitGroup
  17. for i := 0; i < 10; i++ {
  18. wg.Add(1)
  19. go worker(&wg, i)
  20. }
  21. wg.Wait()
  22. }

在以上代码中,我们保证了setup函数只被执行了一次,即使有10个goroutine都尝试去执行setup函数。

条件变量

使用sync.Cond来实现条件变量,这可以使得一个或者多个goroutine等待或者通知事件。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. // Button 定义一个按钮结构体
  8. type Button struct {
  9. Clicked *sync.Cond
  10. }
  11. // 模拟一个用户界面
  12. func simulate(button *Button) {
  13. time.Sleep(time.Second)
  14. // 用户点击button
  15. button.Clicked.Broadcast()
  16. }
  17. func main() {
  18. // 初始化Button
  19. button := Button{Clicked: sync.NewCond(&sync.Mutex{})}
  20. // 显示信息的goroutine
  21. for i := 0; i < 10; i++ {
  22. go func(i int) {
  23. // 等待button被点击
  24. button.Clicked.L.Lock()
  25. defer button.Clicked.L.Unlock()
  26. button.Clicked.Wait()
  27. // button被点击,显示一条信息
  28. fmt.Println("button clicked,", i)
  29. }(i)
  30. }
  31. // 模拟用户点击button
  32. go simulate(&button)
  33. time.Sleep(2 * time.Second)
  34. }

处理并发是使用Go语言开发中非常重要的一部分,理解和掌握sync标准库中的同步原语对于你写出高效可靠的并发程序是非常重要的。

希望这篇文章能够帮助你更好的理解如何在Go语言中进行并发编程,我会非常高兴收到你的反馈~