Go例程与无缓冲channel

基本语法

如下语句创建一个新的go例程,该例程的执行入口为routine1,该语句执行后立即返回:

func routine1() {}
go routine1()

channel是go例程之间通信的方式之一,定义一个channel的语法如下,其中的chantype可以是基本数据类型(如本例的int),或自定义类型:

type chantype int
c := make(chan chantype)

实例:蛋糕店

一个蛋糕的生产需要分别经历烘焙冷却雕花三个步骤,在蛋糕店的流水线上相应地有烘焙师冷却工雕花师三个角色,假设现在的生产任务是3块蛋糕,那么烘焙师完成烘焙后可以将半成品放到产线上,自己继续烘焙第二块蛋糕。冷却工从产线上取出半成品进行冷却,冷却完成后放到产线上,转而继续冷却第二块蛋糕。雕花师此时就可以从产线上取出冷却后的蛋糕进行雕花了。除了第一个蛋糕的生产过程冷却工和雕花师需要等待外,后续流程,三个角色的工作都可以并行进行了。

蛋糕店的定义如下:

type Shop struct {
	cakes        int           // quantity of cakes to be made
	bakeTime     time.Duration // time to bake
	iceTime      time.Duration // time to ice
	inscribeTime time.Duration // time to inscribe
}

烘焙蛋糕

type cake int

func (s Shop) bake(baked chan cake) {
	defer close(baked)

	for i := 0; i < s.cakes; i++ {
		c := cake(i)

		fmt.Println("baking", i)
		work(s.bakeTime)
		fmt.Println(i, "baked")

		baked <- c
	}
}

func work(duration time.Duration) {
	time.Sleep(duration)
}

烘焙师每完成一个蛋糕的烘焙就把半成品放到产线上,其中的work模拟各个角色的工作。baked是一个无缓冲的channel,也就是说烘焙师一次只能投放一个蛋糕,即使第二个蛋糕烘焙完成,也必须等待冷却工从产线上取走蛋糕他才能继续投放。

上述代码中的type cake int定义是为了更好地表达本例的语义,即投放到产线上的是蛋糕,而不是一个没有含义的int

冷却蛋糕

func (s Shop) ice(iced chan cake, baked chan cake) {
	defer close(iced)

	for c := range baked {
		fmt.Println("icing", c)
		work(s.iceTime)
		fmt.Println(c, "iced")

		iced <- c
	}
}

注意这里for循环的区别,冷却工是从产线上(baked)取蛋糕,而不是像烘焙师那样从零开始。对一个无缓冲channel做range会阻塞当前例程,直到有其它例程向该channel发送了数据,或者该channel被close。所以你看到bake里面烘焙师完成所有蛋糕的烘焙后会执行close(baked),以通知冷却工今天的烘焙半成品就这么多,你不要继续再等了。于是冷却工在从baked内取完所有蛋糕后也就可以结束自己的工作了。

雕花

func (s Shop) inscribe(iced chan cake) {
	for c := range iced {
		fmt.Println("inscribing", c)
		work(s.inscribeTime)
		fmt.Println(c, "finished")
	}
}

雕花师的工作与冷却工类似,从产线的iced上取出已被冷却的蛋糕进行雕花,同样的,冷却工在完成所有蛋糕的冷却后需要显式close(iced),以通知雕花师今天的生产任务就这么多,不要再等了。

创建生产线

func (s Shop) Work() {
	baked := make(chan cake)
	iced := make(chan cake)
	
	go s.bake(baked)
	go s.ice(iced, baked)
	s.inscribe(iced)
}

生产线创建,我们先在烘焙师与冷却工之间放置一个工作台baked,以备烘焙师烘焙完成后将半成品放置上去,注意,这个工作台是无缓冲的,也就是一次只能放置一个蛋糕。然后在冷却工与雕花师之间放置一个用以存放冷却后的蛋糕的工作台iced,以备冷却工放置蛋糕供雕花师取用,同样,这个工作台也是无缓冲的。

我们使用了三个例程来模拟三个角色的工作:

  • s.bake:烘焙师例程
  • s.ice:冷却工例程
  • s.inscribe:雕花师例程,也就是主例程

启动生产线

func main() {
	shop := Shop{
		cakes:        3,
		bakeTime:     1 * time.Second,
		iceTime:      2 * time.Second,
		inscribeTime: 3 * time.Second,
	}

	shop.Work()
}

今天的生产任务是3个蛋糕,其中烘焙师烘焙一个蛋糕需要1秒,冷却工冷却一个蛋糕需要2秒,雕花师雕刻一个蛋糕需要3秒。执行效果如下:

baking 0 # 第一个蛋糕在烘焙时,冷却工和雕花师是被阻塞的

0 baked # 第一个蛋糕烘焙完成
baking 1 # 烘焙师继续烘焙第二个
icing 0 # 与此同时,冷却工开始冷却第一个蛋糕

1 baked # 烘焙师完成了第二个蛋糕的烘焙,但是由于冷却工还没有完成第一个蛋糕的冷却,因此烘焙师被阻塞了

0 iced # 冷却工终于完成了第一个蛋糕的冷却
icing 1 # 开始冷却第二个蛋糕
baking 2 # 于是烘焙师也可以投放第二个蛋糕,开始烘焙第三个了
inscribing 0 # 与此同时,雕花师也可以开始雕刻冷却工投放到产线的第一个蛋糕了

2 baked # 烘焙师效率非常高,第三个蛋糕烘焙完成,它关闭了baked,通知冷却工这是最后一个了

1 iced # 冷却工完成了第二个蛋糕的冷却,但是由于雕花师工作比较耗时,第一个蛋糕还没雕刻完成,因此他必须等待

0 finished # 第一个蛋糕出炉
inscribing 1 # 雕刻师开始雕刻第二个蛋糕
icing 2 # 于是冷却工可以开始进行最后一个蛋糕的冷却了

2 iced # 冷却工收工,关闭iced,通知雕花师这是最后一个蛋糕

1 finished # 第二个蛋糕出炉
inscribing 2 # 雕花师进行最后一个蛋糕的雕刻

2 finished # 第三个蛋糕雕刻完成,今天的生产任务圆满完成

完整源码

蛋糕店

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值