Go并发模式:管道和取消

 

WHY?

Go的并发原语可以轻松构建流数据流水线,从而有效利用I/O和多个CPU。

WHAT?

管道是一种数据结构,发送方可以以字符流形式将数据送入该结构,接收方可以从该结构接收数据。

HOW?

Go中没有正式的管道定义;但它是众多并发程序中的一种,是通过通道(channel)连接的一系列阶段,且每个阶段是一组运行同一个函数的goroutine。在每个阶段:goroutine们

1.通过输入通道,从上游的获取数据

2.在数据上执行一系列数据,通常会产生新值

3.通过输出通道,将新产生的值送往下游

每个阶段都拥有任意数量的输入通道和输出通道,除了第一个和最后一个阶段,第一个阶段只有输出通道,最后一个阶段只有输入通道,第一个又被称为来源或生产者,最后一个阶段又被称为槽或消费者。

DO:

在正式代码之前,可以先从HOW之中的叙述大致推理出函数的样子。假设共有三个阶段,则三个阶段的函数形式类似:

func stage1() <-chan {}

func stage2(in <-chan) <-chan{}

func stage(in <-chan){}

这样心里就有数了。如果要求某些数的平方值:该如何运用Go管道的技术呢?

依上述伪代码尝试写一下。

首先第一阶段,要有数字,然后要把这些数以管道的形输出给下一阶段使用:

func genx(nums...int) <-chan int {
	out := make(chan int)
	for n := range nums {
		out <- n
	}

	close(out)

	return out
}

观察这个函数,符不符合上面的WHAT和HOW?

发现并不符合,HOW中要求每一个阶段都是一组goroutine的组合,而这个第一阶段并没有goroutine。所以要用到Goroutine而要用到goroutine且goroutine要满足从通过输入通道从上游获取值,通过输出通道向下游发送值,而且要处理旧值得到新值,意思是所有一切都要在Goroutine中处理,不能直接在函数中处理,要在函数中重开goroutine处理。

改动第一个阶段代码:

func genx(t *testing.T, nums...int) <-chan int {
	out := make(chan int)
	go func() {
		for _, n := range nums {
			out <- n
		}

		t.Logf("genx Closed")
		close(out)
	}()

	return out
}

为什么把把close(out)放在goroutine中执行呢?因为向一个关闭的channel发送数据会引起panic,而goroutine的执行时必 genx的执行顺序慢的,所以如果放在外面,在把nums写入out的时候就会报错。

继续第二阶段,生成平方数:

func sqx(t *testing.T, in <-chan int) <-chan int{
	out := make(chan int)
	go func() {
		for n := range in {
			//t.Logf("NN=%v", n*n)
			out <- n * n
		}

		t.Logf("sqx Closed")
		close(out)
	}()

	return out
}

第三阶段,消费者输出:

func TestX(t *testing.T) {
	a := genx(4, 5, 6)
	b := sqx(t, a)
	go func() {
		for n := range b {
			t.Logf("^o^_%v", n)
		}
	}()
}

这样对吗?运行一下发现,没有任何打印。这是为何?因为TestX运行在主goroutine中,一旦运行完毕程序立刻就退出了,并没有给TestX中的goroutine执行的机会。而我们在TestX的goroutine中调用的是range而不是 <- b这样的函数,不会引起函数的goroutine阻塞,直到有数据读入才停止阻塞。引起阻塞就不会退出主routine,就能看到打印。

所以第三阶段把代码修改为:

func TestX(t *testing.T) {
	a := genx(4, 5, 6)
	b := sqx(t, a)
	
	for n := range b {
		t.Logf("^o^_%v", n)
	}
}

这下可以了,打印出16, 25,36。

这样貌似结束了。

但是还有一些问题。

如果输入的Goroutine不止一个呢?

这里牵涉到两个概念。扇入和扇出。

扇出:多个功能可以从同一个Channel读取,直到该通道关闭为止,提供了一种在一群工作者之间,分配工作给并行化CPU使用和IO的方法。

扇入:从多个输入读取,并继续执行,直到一切都停止下来。当以下情况发生时。通过复用多个输入的Channel到一个单独的Channel。当所有的输入都关闭的时候,这个Channel将会关闭(这个Channel关闭会导致一切【接收输入,输出等】都停止下来。)

扇出的代码:

func TestX1(t *testing.T) {
	in := genx(4, 5, 6)

	// 一个输入点(扇形的顶点),有多引用(扇形的扇面),扇出
	c1 := sqx(t, in)
	c2 := sqx(t, in)
	
	for n := range mergex(c1, c2) {
		t.Logf("n=%v", n)
	}
}

扇入的代码:

// 扇入,很多输入(扇面)࿰
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值