走马戏班

走马戏班是方圆百里最有名的班子,班主姓罗,人称罗老板。戏班每到一处,搭台、唱戏、拆台、走人,有条不紊。

罗老板有一条铁规矩:收场的顺序,一步不能乱。

每场戏结束,流程是这样的:

第一,打收场锣。三声锣响,告诉所有人——戏要散了。

第二,演员退场。台上的角儿们依次走下台,回到后台卸妆。

第三,拆台。布景拆掉,道具装箱,灯绳卷好。

第四,撤棚。戏棚拆解,装车,马匹牵出来,上路。

罗老板说:"这个顺序,是我师父的师父传下来的。谁改,谁倒霉。"


罗老板病倒了

那年秋天,戏班到了柳河镇。第一场《长坂坡》爆满,镇上的人挤满了棚子。

可第二天,罗老板受了风寒,高烧不退。他把副手阿顺叫到床前:"阿顺,今晚的《空城计》,你来主持收场。记住——"

罗老板咳了两声,把收场顺序说了一遍。

阿顺点头:"记住了,记住了。"


阿顺的主意

阿顺是个聪明人。聪明人都喜欢想——能不能更快一点?

晚上《空城计》落幕,阿顺站在台侧,心里盘算:

"打锣——半盏茶。等演员退场——一盏茶。拆台——两盏茶。撤棚——三盏茶。加起来七盏茶的功夫。如果我把顺序调一调呢?"

阿顺的"优化方案"是这样的:

第一步,先撤棚。棚子拆了,回头拆台的时候宽敞。

第二步,拆台。道具装箱,布景卸下来。

第三步,打锣。通知演员——收工了。

第四步,演员退场。但台已经拆了,灯已经卷了,演员摸着黑下台。

阿顺觉得这个顺序"更合理"。于是他开始干。


灾难

阿顺喊来棚工:"撤棚!"

棚工愣住了:"阿顺哥,戏还没散呢。诸葛亮还在城楼上弹琴。"

阿顺说:"没事,先拆外围。不影响。"

棚工们拆了棚布,收了棚绳。台上的诸葛亮弹着琴,头顶的棚子已经没了。

接着阿顺喊:"拆台!"

道具师傅急了:"台上司马懿的兵马还在布景后面候场呢!"

阿顺说:"让他们从侧面绕。"

道具师傅们开始搬布景。城墙刚拆了一半,司马懿带着兵从侧面冲出来了——可城楼已经没了。司马懿站在空荡荡的台上,对着空气演完了"撤兵"那段。

然后是第三步——阿顺想起来,还没打锣。

他拎起铜锣,咣咣咣敲了三下。


台上的诸葛亮正唱着"我正在城楼观山景",锣声一响,他愣住了——戏还没唱完呢,收场锣怎么响了?

后台的琴师还在拉胡琴,突然灯灭了,因为拆台的已经把灯绳收了。

司马懿的兵马从后台绕出来,踩在散落一地的道具上,绊倒了三个。

罗老板在病床上听到动静,挣扎着爬起来,拄着拐杖走到戏棚——看到的是一个拆了一半的台、一群不知所措的演员、和一地狼藉的道具。

罗老板深吸一口气:"阿顺——你把顺序反了。"


为什么顺序不能乱

罗老板把所有人召集起来,坐在拆了一半的台上,说了一段话:

"收场锣必须先打。为什么?因为锣不打,演员不知道要停。 你要拆台,演员还在唱——他踩在正在拆的台板上,摔了算谁的?"

"演员必须先退场。为什么?因为他们在台上,道具就不能收。 人没走,你把椅子撤了,他一屁股坐空,谁负责?"

"拆台必须在撤棚之前。为什么?因为棚子是挡风遮雨的。 你先把棚子拆了,万一突然下雨,道具全淋坏。"

"最后才撤棚。为什么?因为棚子是最外层的壳,所有的东西都在棚子里。 棚子一撤,意味着一切结束。棚子撤了,就不能再有人进出,不能再有东西搬动。"

罗老板看着阿顺:"你觉得自己聪明?把顺序倒过来——先拆壳,再搬东西,再赶人——人还没走,壳就没了。这是什么?这叫拆自己人的台。"

阿顺低着头。

"你记住。"罗老板说,"搭台的时候,从外往里搭。拆台的时候,从里往外拆。 这是最简单、也最重要的道理。你把这个顺序弄反了,不是快慢的问题,是会不会塌的问题。"


代码里的收场锣

罗老板的收场顺序,翻译成程序员的话就是:

  • 收场锣 = exitFlag = true; cv.notify_all();。告诉所有线程——该停了。
  • 演员退场 = worker.join()。等所有线程安全退出,排空手头的工作。
  • 拆台 = 停止依赖的内部服务。Logger 停了,LogPublisher 停了。
  • 撤棚 = 停止最外层的传输层。MqttClient 最后停。

阿顺的"优化"——先撤棚,再拆台,最后赶人——翻译成代码,就是那个导致 112 个 Valgrind 错误的原始 stop()

1
2
3
4
5
6
7
void SystemFactory::stop()
{
MqttClient::getInstance().Stop(); // 先拆棚
DeviceManager::getInstance().stop(); // 再拆台
EmergencyFactory::getInstance().stop(); // 最后赶人
// 没打锣——Logger 的 worker 线程还在跑!
}

棚都拆了,人还在台上唱。这就是 use-after-free。


后记

罗老板病好之后,把收场顺序写成了戏班的班规,刻在一块木板上,挂在后台:

1
2
3
4
5
6
一、锣响三声,告知散场
二、角儿退场,卸妆更衣
三、拆台收箱,布景入笼
四、撤棚装车,马匹上路

顺序不可乱。乱者罚酒三杯,扫台三日。

后来有人问罗老板:"您这收场四步,跟写代码的 stop() 顺序一模一样。您学过编程?"

罗老板说:"我不懂什么编程。但我知道——往外走的时候,先进来的后出去。 你进戏棚,先过棚门,再过台口,最后上台。你出去的时候,得先下台,再过台口,最后出棚门。反着来,就会撞到墙上。"

栈,后进先出。依赖链,最上层先停。罗老板没学过数据结构,但戏班子的收场锣已经敲了三百年。