在 Go 语言中,可以使用信号量来限制并发数或设置多个进程(goroutines)的执行顺序。信号量是一种同步原语,用于控制对共享资源的访问。在 Go 中,信号量的角色通常由带缓冲的通道(buffered channel)来扮演。下面我将提供两个示例,说明如何使用信号量来达到这些目的。
1. 限制并发数
要限制并发数,你可以创建一个带缓冲的通道,其容量为你希望允许的最大并发数。每个 goroutine 在开始执行前都会向通道发送一个信号,如果通道已满,则会阻塞,直到其他 goroutine 完成并释放信号。
下面是一个示例:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// 设置最大并发数为 3
maxConcurrency := 3
semaphore := make(chan struct{}, maxConcurrency)
var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
wg.Add(1)
// 获取信号量
semaphore <- struct{}{}
go func(id int) {
defer wg.Done()
defer func() { <-semaphore }() // 释放信号量
fmt.Printf("Task %d is running\n", id)
time.Sleep(2 * time.Second) // 假设每个任务需要2秒来完成
fmt.Printf("Task %d is done\n", id)
}(i)
}
// 等待所有任务完成
wg.Wait()
fmt.Println("All tasks finished.")
}
在这个例子中,我们创建了一个带缓冲的信号量 semaphore
,其容量是我们希望的最大并发数(在这个例子中是 3)。每个 goroutine 开始之前会向信号量发送一个空结构体,如果信号量满了,就会阻塞。当 goroutine 完成时,它会从信号量中取出一个元素,从而为其他等待的 goroutine 腾出空间。
2. 设置执行顺序
如果你希望按特定的顺序执行多个 goroutine,你可以使用多个通道来控制它们的执行顺序。
下面是一个示例,它使用两个通道来确保第二个操作始终在第一个操作之后执行:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
firstDone := make(chan struct{}) // 用于通知第一个操作完成
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("First operation started")
// 执行第一个操作...
fmt.Println("First operation finished")
close(firstDone) // 发送完成信号
}()
wg.Add(1)
go func() {
defer wg.Done()
<-firstDone // 等待第一个操作完成的信号
fmt.Println("Second operation started")
// 执行第二个操作...
fmt.Println("Second operation finished")
}()
wg.Wait() // 等待所有 goroutine 完成
}
在这个例子中,第一个 goroutine 完成后会关闭 firstDone
通道,这会向所有等待在该通道上的 goroutine 发送信号,告知它们第一个操作已经完成,可以继续执行。第二个 goroutine 会等待这个信号,然后开始执行。
3. 控制多个进程(goroutine)的交替工作
要控制多个 goroutine 的交替执行,可以使用通道在它们之间进行同步。
package main
import (
"fmt"
"sync"
)
func worker(id int, beginWork <-chan struct{}, endWork chan<- struct{}, wg *sync.WaitGroup) {
defer wg.Done()
for {
// 等待开始工作的信号
_, ok := <-beginWork
if !ok {
// 如果通道关闭了,退出循环
return
}
// 开始工作
fmt.Printf("Worker %d: started working\n", id)
// 模拟工作时间
fmt.Printf("Worker %d: finished working\n", id)
// 发出完成工作的信号
endWork <- struct{}{}
}
}
func main() {
var wg sync.WaitGroup
beginWork := make(chan struct{})
endWork := make(chan struct{})
// 启动两个 worker
for i := 1; i <= 2; i++ {
wg.Add(1)
go worker(i, beginWork, endWork, &wg)
}
// 开始交替执行工作
for i := 1; i <= 5; i++ {
// 发出开始工作的信号
beginWork <- struct{}{}
// 等待工作完成的信号
<-endWork
}
// 完成所有工作后关闭 beginWork 通道
close(beginWork)
// 等待所有 worker 完成
wg.Wait()
fmt.Println("All workers finished.")
}
在这个例子中,我们创建了两个通道 beginWork
和 endWork
来控制工作流程。beginWork
用于通知 worker 开始工作,endWork
用于 worker 完成工作后发送信号。我们启动了两个 worker,它们会交替接收开始和结束工作的信号。
通过这种方法,可以确保每个 worker 按顺序完成工作。主函数在通知一个 worker 开始工作后会等待工作完成的信号,然后再通知下一个 worker。当所有工作完成后,主函数关闭 beginWork
通道,这会导致所有 worker 退出循环并完成执行。
使用信号量通道来控制并发和执行顺序是 Go 并发模型的一个强大特性,它使得同步和协作变得相对简单。