Bootstrap

Go语言中的条件变量:sync.NewCond

在并发编程中,我们经常需要在多个goroutine之间同步操作,特别是当某些操作需要等待特定条件发生时。Go语言的sync包提供了一个强大的同步原语——条件变量(Cond),它允许我们等待或通知某个条件的变化。

sync.NewCond

sync.NewCond函数用于创建一个新的条件变量,并将其与一个互斥锁(sync.Mutexsync.RWMutex)关联。这个条件变量可以用来挂起和唤醒goroutine,以便在满足特定条件时协调它们的执行。

条件变量的方法

  • L:条件变量关联的互斥锁。
  • Wait():当前goroutine在调用此方法后会被挂起,直到被SignalBroadcast唤醒。
  • Signal():唤醒一个等待条件变量的goroutine(如果有的话)。
  • Broadcast():唤醒所有等待条件变量的goroutine。

示例代码

下面是一个使用sync.NewCond的示例,模拟了一个比赛场景,其中10个选手需要等待一个开始信号才能开始比赛。

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	race()
}

// race 函数模拟了一个比赛场景,10个选手等待一个信号开始比赛
func race() {
	// 创建一个条件变量和互斥锁
	cond := sync.NewCond(&sync.Mutex{})
	// 创建一个等待组,用于等待所有选手完成比赛
	var wg sync.WaitGroup
	// 初始化等待组,添加11个任务(10个选手 + 1个发令员)
	wg.Add(11)

	// 启动10个选手的goroutine
	for i := 0; i < 10; i++ {
		go func(num int) {
			// 选手完成后,将等待组计数器减一
			defer wg.Done()
			// 打印选手就位信息
			fmt.Println(num, "号已经就位")
			// 选手获取互斥锁,准备等待信号
			cond.L.Lock()
			// 选手等待条件变量的信号
			cond.Wait()
			// 打印选手开始工作的信息
			fmt.Println(num, "号已开始工作")
			// 选手释放互斥锁
			cond.L.Unlock()
		}(i)
	}

	// 等待2秒,模拟比赛准备时间
	time.Sleep(2 * time.Second)

	// 启动一个goroutine,用于发送比赛开始的信号
	go func() {
		// 完成后,将等待组计数器减一
		defer wg.Done()
		// 打印准备开始的信息
		fmt.Println("准备开始!")
		// 发送广播信号,通知所有等待的选手开始比赛
		cond.Broadcast()
	}()

	// 等待所有选手完成比赛
	wg.Wait()
}

工作原理

在这个示例中,我们首先创建了一个条件变量cond,并将其与一个新的sync.Mutex关联。然后,我们启动了10个goroutine来模拟选手,每个选手在就位后会调用cond.Wait()来挂起自己。这会导致它们释放互斥锁并等待信号。

主goroutine等待2秒钟后,启动一个发令员goroutine,它调用cond.Broadcast()来唤醒所有等待的选手。当选手们被唤醒后,它们会重新获取互斥锁,打印开始工作的信息,然后释放互斥锁。

应用场景

条件变量非常适合用于需要等待特定事件或条件发生的场景,例如:

  • 生产者-消费者问题,消费者等待生产者生产数据。
  • 信号量控制,限制同时访问某个资源的goroutine数量。
  • 任何需要在多个goroutine之间同步执行的场景。

总结

sync.NewCond是Go语言中一个非常实用的同步原语,它提供了一种简单而有效的方式来协调多个goroutine的执行。通过使用条件变量,我们可以编写出更加灵活和高效的并发程序。在处理复杂的并发问题时,sync.NewCond是一个不可或缺的工具。

;