Go语言并发之道学习六 管道的扇入 or-done-channel tee-channel 桥接channel

本文深入探讨了Go语言中管道通信的高级应用,包括扇入扇出、or-done-channel、tee-channel和桥接channel等模式,展示了如何优化数据流处理,提高多核CPU效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

管道的扇入扇出 or-done-channel tee-channel 桥接channel

package main

import (
	"fmt"
	"time"
	"sync"
	"runtime"
	"math/rand"
)

func main() {
	fmt.Println("pipe1")
	pipe1()
	fmt.Println()
	fmt.Println("ordonechannel")
	ordonechannel()
	fmt.Println()
	fmt.Println("teechannel")
	teechannel()
	fmt.Println()
	fmt.Println("bridgechannel")
	bridgechannel()
}

func pipe1(){
	repeatFn := func(
		done <-chan interface{},
		fn func() interface{},
		)<-chan interface{}{
		numStream := make(chan interface{})
		go func() {
			defer close(numStream)
			for{
				select {
				case <-done:
					return
				case numStream<-fn():
				}
			}
		}()
		return numStream
	}
	toInt := func(
		done <-chan interface{},
		valueStream <-chan interface{},
		) <-chan int{
		intStream := make(chan int)
		go func() {
			defer close(intStream)
			for v := range valueStream{
				select {
				case <- done:
					return
				case intStream<-v.(int):
				}
			}
		}()
		return intStream
	}

	//查找素数
	primeFinder := func(
		done <-chan interface{},
		randStream <-chan int,
		) <-chan interface{}{
		primeStream := make(chan interface{})
		go func() {
			defer close(primeStream)
			for integer := range randStream {
				if integer <= 1{
					continue
				}
				prime := true
				for divisor := integer -1;divisor > 1;divisor--{
					if integer%divisor == 0{
						prime = false
						break
					}
				}
				if prime {
					select {
					case <-done:
						return
					case primeStream<-integer:
					}
				}
			}
		}()
		return primeStream
	}

	take := func(
		done <-chan interface{},
		primeStream <-chan interface{},
		num int,
		) <-chan interface{}{
		takeStream := make(chan interface{})
		go func() {
			defer close(takeStream)
			for i := num;i > 0;i--{
				select {
				case <-done:
					return
				case takeStream<-<-primeStream:
				}
			}
		}()
		return takeStream
	}

	rand.Seed(time.Now().UnixNano()) //利用当前时间的UNIX时间戳初始化rand包
	rand := func() interface {}{return rand.Intn(5000000)}

	done := make(chan interface{})
	defer close(done)

	start1 := time.Now()

	randIntStream := toInt(done,repeatFn(done,rand))
	fmt.Println("Primes:")
	for prime := range take(done,primeFinder(done,randIntStream),10){
		fmt.Printf("prime1 %d\n",prime)
	}
	fmt.Printf("search1 took: %v\n", time.Since(start1))
	fmt.Println()

	//-----------------------------
	//优化stage ,使用多个CPU
	/*
	因为我们有多个cpu 核心,我们可以启动 primeFinder 这个stage的许多副本
	现在我们将有 cpu 数量的 goroutine 从随机数发生器中拉出并试图确定数字是否为素数。
	*/
	//扇入意味着将多个数据流复用或合并成一个流:
	fanIn := func(
		done <-chan interface{},
		channels ...<-chan interface{},
		) <-chan interface{}{
		var wg sync.WaitGroup
		multiStream := make(chan interface{})
		multi := func(c <- chan interface{}) {
			defer wg.Done()
			for i := range c {
				select {
				case <- done:
					return
				case multiStream<-i:
				}
			}
		}
		// 从所有channel里取值
		wg.Add(len(channels))
		for _,c := range channels{
			go multi(c)
		}
		// 等待所有的读操作结束
		go func() {
			wg.Wait()
			close(multiStream)
		}()
		return multiStream
	}

	done2 := make(chan interface{})
	defer close(done2)

	start2 := time.Now()

	numFinders := runtime.NumCPU()
	fmt.Printf("Spinning up %d prime2 finders\n",numFinders)
	finders := make([]<-chan interface{},numFinders)
	for i := 0;i < numFinders ;i++  {
		finders[i] = primeFinder(done,randIntStream)
	}
	for prime := range take(done,fanIn(done,finders...),10){
		fmt.Printf("prime2 %d\n",prime)
	}
	fmt.Printf("search2 took: %v\n",time.Since(start2))


}

// or-done-channel
func ordonechannel(){
	/*
	有时候,我们在处理来自系统各个分散部分的channel时,并不知道我们的 goroutine是否被取消。
	我们需要用channel 的select 语句来包装我们的读操作,并从中选择正确的channel
	*/
	total := 3 //共有3个 valueStream
	channels := func(done <-chan interface{}) <-chan interface{}{
		valueStream := make(chan interface{})
		go func() {
			defer close(valueStream)
			for i := 0;i <= total;i++{
				select {
				case <- done:
					return
				case valueStream<-i:
				}
			}
		}()
		return valueStream
	}
	orDone := func(
		done <-chan interface{},
		channel <-chan interface{},
		)<-chan interface{}{
		valueStream := make(chan interface{})
		go func() {
			defer close(valueStream)
			i := 0
			for{
				//当到达 i = 2 channel 时,我们提前取出 channel 的内容 ,channel 就会关闭了
				//这样后面的select case 取出 channel 时, ok 就会接收到 false
				if i == 2 {
					<-channel
				}
				i++
				select {
				case <-done:
					return
				case v,ok := <-channel:
					fmt.Printf("v %v -- ok %v\n",v,ok)
					//如果是已关闭的 channel ,我们就不用进行操作了
					if ok == false{
						return
					}
					select {
					case <-done:
					case valueStream<-v:
					}
				}
			}
		}()
		return valueStream
	}

	done := make(chan interface{})
	defer close(done)

	for val := range orDone(done,channels(done)){
		fmt.Printf("orDone %v\n",val)
	}
}

//tee-channel
func teechannel(){
	/*
	tee-channel 可以将一个传递的读channel ,返回两个单独的channel ,以获取两个相同的值:
	*/
	orDone := func(
		done <-chan interface{},
		intStream <-chan interface{},
	) <-chan interface {}{
		valueStream := make(chan interface{})
		go func() {
			defer close(valueStream)
			for {
				select {
				case <-done:
					return
				case val,ok := <-intStream:
					if ok == false {
						return
					}
					valueStream<-val
				}
			}
		}()
		return valueStream
	}

	tee := func(
		done <- chan interface{},
		in <-chan interface{},
		)(_,_ <-chan interface{}){
		out1 := make(chan interface{})
		out2 := make(chan interface{})
		go func() {
			defer close(out1)
			defer close(out2)
			for val := range orDone(done,in) {
				var out1, out2 = out1,out2
				for i := 0;i < 2 ;i++  {
					select {
					case <- done:
					case out1<-val:
						out1 = nil
					case out2<-val:
						out2 = nil
					}
				}
			}
		}()
		return out1,out2
	}

	repeat := func(
		done <-chan interface{},
		nums ...int,
		) <-chan interface{}{
		intStream := make(chan interface{})
		go func() {
			defer close(intStream)
			for{
				for num := range nums{
					select {
					case <-done:
						return
					case intStream<-num:
					}
				}
			}
		}()
		return intStream
	}

	take := func(
		done <-chan interface{},
		intStream <-chan interface{},
		num int,
		) <-chan interface{}{
		takeStream := make(chan interface{})
		go func() {
			defer close(takeStream)
			for i := 0;i < num;i++{
				select {
				case <-done:
					return
				case takeStream<-<-intStream:
				}
			}
		}()
		return takeStream
	}

	done := make(chan interface{})
	defer close(done)

	out1,out2 := tee(done,take(done,repeat(done,1,2),4))

	for val1 := range out1{
		fmt.Printf("out1: %v, out2: %v\n",val1,<-out2)
	}
	/*
	·var out1, out2 = out1,out2
	我们将要使用的是out1 和 out2 的本地版本,所有我们会隐藏这些变量。

	·for i := 0;i < 2 ;i++  {
	我们将使用一条select 语句,以便不阻塞的写入out1 和 out2
	为确保两个都被写入 ,我们将执行select 语句的两次迭代:每个出站一个 channel

	·out1 = nil out2 = nil
	一旦我们写入了channel ,我们将其影副本设置为nil ,以便进一步阻塞写入,而另一个channel 可以继续

	注意写入out1 和out2 是紧密耦合的。
	知道out1 和out2 都被写入,迭代才能继续。
	*/
}

//桥接channel 模式
func bridgechannel(){
	/*
	我们将一个充满channel 的channel 拆解为一个简单的channel ,称为桥接channel
	*/
	orDone := func(
		done <-chan interface{},
		valueStream <-chan interface{},
		)<-chan interface{}{
		doneStream := make(chan interface{})
		go func() {
			defer close(doneStream)
			for{
				select {
				case <-done:
					return
				case val,ok := <-valueStream:
					if ok == false{
						return
					}
					doneStream<-val
				}
			}
		}()
		return doneStream
	}
	//桥接channel
	bridge := func(
		done <-chan interface{},
		chanStream <-chan <-chan interface{},
		)<-chan interface{}{
		valueStream := make(chan interface{})
		go func() {
			defer close(valueStream)
			for{
				var stream <-chan interface{}
				select{
				case maybeStream,ok := <-chanStream:
					if ok == false{
						return
					}
					stream = maybeStream
				case <-done:
					return
				}
				for val := range orDone(done,stream){
					select{
					case <-done:
					case valueStream <-val:
					}
				}
			}
		}()
		return valueStream
	}
	/*
	·valueStream := make(chan interface{})
	这是将返回的bridge 中的所有值的channel

	·for{
	这个循环负责从chanStream 中提取channel 并将其提供给嵌套循环来使用

	·for val := range orDone(done,stream){
	该循环负责读取已经给出的channel 中的值,并将这些值重复到valueStream中。
	当我们当前正在循环的流关闭时,我们从执行从此channel 读取的循环中跳出,并继续循环的下一次迭代,
	选择要读取的channel ,这为我们提供了一个不间断的结果值的流。
	*/
	generator := func(max int)<-chan <-chan interface{}{
		chanStream := make(chan (<-chan interface{}))
		go func() {
			defer close(chanStream)
			for i:=0;i<max;i++{
				stream := make(chan interface{},1)
				stream<-i
				close(stream)
				chanStream<-stream
			}
		}()
		return chanStream
	}
	done := make(chan interface{})
	defer close(done)
	for v := range bridge(done,generator(10))  {
		fmt.Printf("bridge %v\n",v)
	}

}



 

本书作者带你一步一步深入这些方法。你将理解 Go语言为何选定这些并发模型,这些模型又会带来什么问题,以及你如何组合利用这些模型中的原语去解决问题。学习那些让你在独立且自信的编写与实现任何规模并发系统时所需要用到的技巧和工具。 理解Go语言如何解决并发难以编写正确这一根本问题。 学习并发与并行的关键性区别。 深入到Go语言的内存同步原语。 利用这些模式中的原语编写可维护的并发代码。 将模式组合成为一系列的实践,使你能够编写大规模的分布式系统。 学习 goroutine 背后的复杂性,以及Go语言的运行时如何将所有东西连接在一起。 作者简介 · · · · · · Katherine Cox-Buday是一名计算机科学家,目前工作于 Simple online banking。她的业余爱好包括软件工程、创作、Go 语言(igo、baduk、weiquei) 以及音乐,这些都是她长期的追求,并且有着不同层面的贡献。 目录 · · · · · · 前言 1 第1章 并发概述 9 摩尔定律,Web Scale和我们所陷入的混乱 10 为什么并发很难? 12 竞争条件 13 原子性 15 内存访问同步 17 死锁、活锁和饥饿 20 确定并发安全 28 面对复杂性的简单性 31 第2章 对你的代码建模:通信顺序进程 33 并发与并行的区别 33 什么是CSP 37 如何帮助你 40 Go语言并发哲学 43 第3章 Go语言并发组件 47 goroutine 47 sync包 58 WaitGroup 58 互斥锁和读写锁 60 cond 64 once 69 池 71 channel 76 select 语句 92 GOMAXPROCS控制 97 小结 98 第4章 Go语言并发模式 99 约束 99 for-select循环103 防止goroutine泄漏 104 or-channel 109 错误处理112 pipeline 116 构建pipeline的最佳实践 120 一些便利的生成器 126 扇入,扇出 132 or-done-channel 137 tee-channel 139 桥接channel模式 140 队列排队143 context包 151 小结 168 第5章 大规模并发 169 异常传递169 超时和取消 178 心跳 184 复制请求197 速率限制199 治愈异常的goroutine 215 小结 222 第6章 goroutine和Go语言运行时 223 工作窃取223 窃取任务还是续体 231 向开发人员展示所有这些信息 240 尾声 240 附录A 241
消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。消息中间件适用于需要可靠的数据传送的分布式环境。采用消息中间件机制的系统中,不同的对象之间通过传递消息来激活对方的事件,完成相应的操作。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者。消息中间件能在不同平台之间通信,它常被用来屏蔽掉各种平台及协议之间的特性,实现应用程序之间的协同,其优点在于能够在客户和服务器之间提供同步和异步的连接,并且在任何时刻都可以将消息进行传送或者存储转发,这也是它比远程过程调用更进一步的原因。在了解消息中间件之前,首先了解两个基本概念Message和Queue。Message :消息“消息”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。所以主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时由于使用了消息队列,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦和。这也是消息中间件的意义所在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值