Go 使用信号量限制并发数和控制多个进程的执行顺序

在 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.")
}

在这个例子中,我们创建了两个通道 beginWorkendWork 来控制工作流程。beginWork 用于通知 worker 开始工作,endWork 用于 worker 完成工作后发送信号。我们启动了两个 worker,它们会交替接收开始和结束工作的信号。

通过这种方法,可以确保每个 worker 按顺序完成工作。主函数在通知一个 worker 开始工作后会等待工作完成的信号,然后再通知下一个 worker。当所有工作完成后,主函数关闭 beginWork 通道,这会导致所有 worker 退出循环并完成执行。

使用信号量通道来控制并发和执行顺序是 Go 并发模型的一个强大特性,它使得同步和协作变得相对简单。

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值