以一个代码示例分析Go语言使用无缓冲通道实现的管道(pipeline)工作机制

一、代码示例

package main
import (
	"fmt"
	"math/rand"
)
var done = false//全局变量
var Mess = make(map[int]bool)//全局变量
func main(){
	A:=make (chan int) //无缓冲通道
	B:=make (chan int) //无缓冲通道
	go sendRan(50,10,A)
	go receive(B,A)
	sum(B)
}

func genRandom(max,min int) int {
	return rand.Intn(max-min) + min //func (r *Rand) Intn(n int) int,返回一个取值范围在[0,n)的伪随机int值,如果n<=0会panic。
}

func sendRan(max,min int, out chan <-int) {
	for {
		if done {
			close(out)
			return
		}
		out <- genRandom(max,min)
	}
}

func receive(out chan<-int,in <-chan int){
	for r := range in {
		fmt.Println(" ",r)
		_,ok :=Mess[r]//Mess是映射,如果当前并没有key为r对应的key-value对,那么变量Mess[r]取值为默认的false,赋给变量ok
		if ok { //如果执行了这一行的代码块,至少说明Mess[r]有key-value对,且value为true
			fmt.Println("duplicate num is :",r)
			done = true
		} else {
			Mess[r] = true//如果ok为false,说明Mess这个映射并没有key为r所对应的value,没有相应的key-value对,此时通过Mess[r]=true,为Mess新增一个key-value对,key就是r
			out <- r
		}
	}
	close(out)
}

func sum(in <-chan int) {
	var sum int
	for r:= range in {
		sum += r
	}
	fmt.Println("The sum is:",sum)
}

二、解析

这个代码的功能是生成随机整数并计算它们的总和,同时检查是否有重复的数字出现。
程序中包含以下几个函数:

genRandom: 这个函数用于生成一个在 [min, max) 范围内的随机整数。

sendRan: 这个函数用于生成随机整数,并将它们发送到通道 out 中。如果 done 变量被设置为 true,则关闭通道 out,结束循环。

receive: 这个函数用于从通道 in 中接收随机整数,并检查是否有重复的数字。如果有重复的数字,将 done 设置为 true,以通知其他协程停止生成随机数。否则,将随机数发送到通道 out 中。

sum: 这个函数从通道 in 中接收随机整数,并计算它们的总和。

在 main 函数中,创建了两个无缓冲通道 A 和 B。然后启动了三个 goroutine:一个用于生成随机数并发送到通道 A,一个用于接收从 A 通道传递过来的随机数并处理它们,最后一个用于计算随机数的总和。这样,通过通道的使用,这三个 goroutine 之间可以进行协调和同步,确保程序正确地处理随机数并计算总和。

三、如何体现的pipeline(管道)

管道其实就是通过通道(channel)的阻塞功能,将多个go程的执行逻辑串联起来(互动、相互制约)。如果多个go程之间,一个goroutine的输出是另一个goroutine的输入,这种行为就是pipeline。

在上述代码中,sendRan这个goroutine会向通道A写入数据,由于通道A和B都是无缓冲的通道,因此A会随即阻塞该go程,直到有go程从通道A取出数据
并发运行的另外一个go程receive则会通过for…range…循环遍历A通道的数据,从A通道中取走数据,一次一次地使得sendRan这个go程解除阻塞。在取数据时:
1、会对数据r进行判断,如果r当前并不存在全局变量Mess中(具体来说是存在Mess的[int]中),那么会以r为key,true为value,向Mess写入一个新的元素(代码Mess[r]=true实现了此功能),同时,将随机数r写入到通道B中。每一次的写入都会造成receiveg这个go程的阻塞,依赖于主go中sum函数从B中取走数据来解除阻塞
2、如果收到的随机数r已经作为一个key存在于Mess中,那么肯定其value就是true(第1步实现),则执行if ok后面的语句,会将全局变量done赋值为true。
由于 sendRan 函数是一个无限循环,它会持续地向通道 A 中发送随机数,直到 done 被设置为 true。
在 receive 函数中的 for r := range in 循环会一直迭代,直到通道 A 关闭。由于通道 A 是由 sendRan 函数关闭的,所以当 sendRan 函数退出循环时,会执行 close(A) 关闭通道 A。这时 receive 函数中的 for r := range in 循环就会结束,receive 函数执行 close(out) 关闭通道 B。
主go中的sum函数从通道B读取数据,从而解除receive函数go程的阻塞,和receive这个go程实现了联动(因为receive不向B通道写数据,主go也无法读取数据而只能一直阻塞),同时sum函数将从B中读取的数据做累加操作,计算和。

四、应用场景

这种使用无缓冲通道来协调多个 goroutine 的工作模式,在实际工作中有许多应用场景。这种模式通常被称为 “无缓冲通道同步”,它可以实现一些重要的同步和协调操作,确保程序的正确性和可靠性。

一些常见的应用场景包括:

任务分发与处理:可以使用无缓冲通道来将任务分发给多个处理器(goroutine),并等待所有任务处理完成后再继续执行。

数据流处理:多个 goroutine 可以协同处理一个数据流,其中一个 goroutine 负责数据的生成,而其他 goroutine 负责数据的处理,通过无缓冲通道进行数据传递。

线程池:使用无缓冲通道来管理固定数量的工作线程,将任务发送到通道中,由工作线程进行处理。

事件通知:一个 goroutine 可以向无缓冲通道发送事件,而其他 goroutine 则通过监听该通道来接收并处理事件。

控制并发:通过使用无缓冲通道来限制并发数量,例如使用一个有限数量的通道作为信号量,控制同时进行的并发操作数量。

同步协程:在多个 goroutine 之间进行同步和等待,确保它们按照特定顺序执行,或者在某些条件满足后再同时开始执行。

这些应用场景中,使用无缓冲通道来实现同步的关键在于阻塞特性,当通道没有准备好接收或发送数据时,goroutine 会被阻塞,从而实现了协程之间的同步和协作。这种方式可以避免显式的锁和条件变量,从而简化并发编程的复杂性,使代码更易于维护和调试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vSeanere

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值