Go 宗门第三课:分身打完怪,战利品去哪了?

0 阅读7分钟

Go 宗门第三课:分身打完怪,战利品去哪了?

墨余没有离开 Go 宗门。

长老告诉他,想拿到外派资格,至少还要通过三场 Go 并发试炼。

第一场,他学会了用 go 启动分身。

第二场,他学会了不能让多个分身同时修改同一份数据。

第三场,考验的是另一件事:

分身完成任务之后,怎么把结果交回来?

这一次的任务地点在 Go 宗门后山。

那里有一座废弃的传讯阵,过去负责把任务分发给不同的分身,再把它们完成任务后的结果送回主阵。

最近几天,传讯阵里的结果总会莫名其妙地消失。

任务明明完成了,主阵却收不到任何回报。

有人说,是山里的吞讯兽醒了。

墨余站在传讯阵前,看着地上断裂的蓝色阵纹。

“只要把吞讯兽打败,结果自然就回来了。”

零序漂浮在他身后。

“你怎么知道?”

“因为怪物通常都喜欢躲在问题最明显的地方。”

“那它在哪?”

零序指向传讯阵中央。

那里没有怪物,只有一条空荡荡的石槽。

“它可能就躲在‘没有传回来’这件事里。”

墨余没有听懂。

长老把三枚任务玉简放进阵台。

“三个分身分别去处理三枚玉简。它们打完怪之后,必须把结果传回主阵。”

“这还不简单?”

墨余抬手。

“go!”

三道青色分身同时冲入后山。

第一道分身对付裂岩兽。

第二道分身追捕黑雾蛛。

第三道分身则去寻找偷走灵石的吞讯兽。

墨余站在主阵旁边等待。

过了一会儿,第一道分身回来了。

它手里拿着一块裂岩兽的獠牙。

墨余伸手去接。

可分身只是站在原地,什么也没有交出来。

“你倒是把结果给我啊。”墨余说。

分身看着他,像是听不见一样。

“它只是分身,又不会说话。”

墨余想了想,抓起一块白色阵盘,在上面写下“裂岩兽已击败”,然后塞进分身手里。

“这样总行了吧?”

分身拿着阵盘,转身又跑回了后山。

墨余愣住了。

没过多久,第二道分身也回来了。

它的手里没有阵盘,只有一团被烧焦的黑雾。

墨余赶紧伸手去接。

黑雾刚碰到他的手指,便散成了几缕烟。

第三道分身回来得更晚。

它带回了吞讯兽的一只黑色眼睛,却在靠近主阵时突然消失。

三项任务都完成了。

主阵却只记录下了一片空白。

墨余的脸色沉了下来。

“吞讯兽真的把结果吃了。”

零序摇头。

“不是它吃的。”

“那结果去哪了?”

“你只是派它们出去,从来没有给它们一条回来的路。”

墨余看向后山。

三道青色身影还在山谷之间来回奔跑。

它们知道任务已经完成,却不知道结果应该交给谁,也不知道什么时候算真正完成。

这时,地面忽然裂开。

一只长着三张嘴的黑色怪物从传讯阵下钻了出来。

它的每一张嘴里,都叼着一段没有送达的任务结果。

第一张嘴里,是裂岩兽的獠牙。

第二张嘴里,是烧焦的黑雾。

第三张嘴里,是那只已经消失的黑色眼睛。

吞讯兽把三段结果一起吞了下去,身体立刻膨胀起来。

“看吧。”墨余拔出短刃,“我就说是它。”

零序没有阻止他。

“这一次,你可以打它。”

墨余冲了出去。

三个分身也同时扑向吞讯兽。

一个分身把獠牙砸向怪物。

一个分身把黑雾缠向怪物。

最后一个分身则试图从怪物嘴里抢回黑色眼睛。

吞讯兽被打得连连后退,三张嘴却同时张开,把三份结果吐向三个不同方向。

结果再次飞散。

墨余追上其中一份,抓住了黑雾。

可另外两份已经消失在山雾里。

“这样下去,打死它也没用。”零序说。

“那怎么办?”

“给分身修一条传话的路。”

零序抬起手,在主阵和三个分身之间画出一道蓝色光线。

光线没有连接三座山峰,也没有连接三个人。

它只连接了两个点:

发送结果的分身。

接收结果的主阵。

“这不是新的分身。”零序说,“也不是共享仓库。”

“它是一条只能传递消息的通道。”

墨余看着那道光。

“传话阵?”

“在 Go 里,它叫 channel。”

吞讯兽再次扑来。

墨余没有让分身把结果塞进同一块灵石,也没有让它们把结果随手丢在地上。

他在主阵中央凝出一条蓝色传话阵。

“go!”

第一道分身击退裂岩兽后,把獠牙放进传话阵。

传话阵另一端立刻亮起。

墨余伸手接住了獠牙。

第二道分身把黑雾送入传话阵。

墨余接住黑雾。

第三道分身抓住黑色眼睛,刚要把它放进去,吞讯兽已经扑到了身后。

墨余没有回头。

“把结果传回来!”

第三道分身纵身跃起,把黑色眼睛投入传话阵。

结果没有被吞讯兽抢走。

它沿着蓝色光线穿过山谷,稳稳落入墨余掌心。

吞讯兽扑了个空,撞在传讯阵上。

三张嘴同时发出尖叫。

墨余将三份结果放回主阵。

传讯阵上的三道阵纹逐一亮起。

任务完成。

吞讯兽的身体迅速缩小,最后变成一团没有方向的黑雾,被阵台吸了进去。

长老的声音从山口传来。

“这一次,你终于让分身把结果交回来了。”

墨余擦掉额头上的汗。

“所以 channel 就是装东西的通道?”

“不准确。”

长老走到传讯阵旁边。

“channel 不是普通仓库。它是一条用于 goroutine 之间通信的通道。一个 goroutine 把值发送进去,另一个 goroutine 从里面接收。”

墨余看着手里的三份结果。

“那如果没有人接呢?”

“如果是无缓冲 channel,发送方会等接收方准备好。接收方也会等发送方提供数据。”

长老指向那道蓝色光线。

“正因为要等待,channel 不只是在传递数据,也能让两个 goroutine 建立协作关系。”

墨余想起刚才的场面。

如果没有传话阵,分身打完怪只能各自拿着结果乱跑。

有了传话阵,它们才知道结果应该交给谁。

他忽然明白,协作不只是“不抢同一份数据”。

还包括把任务结果可靠地交给下一个人。

把传话阵换成 Go 代码

下面的代码让一个 goroutine 完成任务,再通过 channel 把结果传回 main

package main

import "fmt"

func 打怪(result chan string) {
	result <- "吞讯兽已击败"
}

func main() {
	result := make(chan string)

	go 打怪(result)

	message := <-result
	fmt.Println(message)
}

这里的 result 就是一条传话通道。

打怪 函数运行在新的 goroutine 中。任务完成后,它通过:

result <- "吞讯兽已击败"

把字符串发送到 channel。

main 函数通过:

message := <-result

接收这条消息。

这段代码里,发送和接收会互相等待:

  • 如果 打怪 先发送,但 main 还没有接收,发送方会等待;
  • 如果 main 先接收,但 goroutine 还没有发送,接收方会等待;
  • 只有发送和接收都准备好,消息才能完成传递。

这就是无缓冲 channel 的基本行为。

channel 不是用来随意存数据的数组,也不是用来保护共享变量的锁。它的主要作用是让 goroutine 通过传递消息进行协作。

这一章先记住:

不要让 goroutine 抱着结果四处乱跑,用 channel 把结果可靠地传给需要它的人。

chapter-03-channel-v1.png

图里的蓝色传话阵,就是 channel 的直观比喻:左侧的分身负责发送结果,右侧的主阵负责接收结果,中间的通道负责让双方完成协作。

墨余收起传话阵。

他本以为自己已经掌握了 go 和互斥锁,没想到 Go 宗门真正的修行才刚刚开始。

传话阵的另一端,忽然传来一声沉闷的撞击。

长老的脸色变了。

“别急着高兴。”

“怎么了?”

“如果两个分身互相等着对方传话,谁都不肯先动……”

传话阵中的蓝光瞬间凝固。

后山深处,第二只怪物睁开了眼睛。

它的名字叫——死锁。