遍历删除

今天想实现个小功能:战斗结束时,后台先立即退房间
  • 退房间逻辑包含了:所有人退光后,销毁房间
  • 销毁房间,会同步销毁GameWorld
在OnPlayerDie回调中加了一句代码,导致连续出现四处宕机bug~太牛逼了
  • Bug分属四处不同模块
  • 均是同种原因:遍历中途,删元素,上层持有的引用失效
很好修复,但这不是重点。重点是,这bug同时隐匿在
  • 系统最底层,碰撞检查器,遍历调用OnTriggerEnter
    • OnTriggerEnter 参数中的某些对象已被销毁
  • 系统底层,GameWorld update
  • 系统底层,GameWorld内置计时器
    • GameWorld都没了,还在跑遍历
  • 业务层,OnPlayerDie遍历队伍,通知调用链中,触发退队,迭代器失效
分别不同人写的模块,且不做本次业务改动,跑得很稳定
  • 也就是说:在写模块时,开发者没想到这bug。这里有两层缘故:
  • 一是,迭代遍历,太常见,太顺手了 …… 依赖个人警惕,非常不靠谱
    • 交叉review靠谱点,但现实里review是需要成本的,极少团队愿意支付这个成本
  • 二是,上述模块均带回调事件
    • 回调链,究竟多长,会做啥 …… 不受起初写该模块人的控制
    • 是个需求上就非内聚的东西
 
本质是:持有着某对象的引用,调用事件函数后,共享出去了它的操作权,但写的人忘了它会被改,更不能期望别人会主动通知。
 
根本解决方案:支持一套迭代安全的容器模板,仅高内聚的模块允许使用原生迭代器
  • 预想的坑:回调链中,可能再次调用到遍历(函数重入),常规类中记个游标的方式,也是不安全的
  • 标记删除,或许更好
 
【二版修正】
GameWorld定时器遍历宕机,不属于“迭代遍历删元素”bug,它是因为定时器回调里销毁了GameWorld
是个架构问题,作为最底层的GameWorld一类的,它的真正销毁、删元素权限,不该暴露给业务上层
  • 给上层的接口,都是标记式的
【扩展联想】
  • 析构比构造难多了
    • c艹的确定性析构,需要开发者准确掌握资源回收的时序
    • 业务层的资源回收步骤,往往带着很长的调用链
  • 写这个,有种网上描述屎山代码的体验~改个小地方,崩一片
    • 如若是线上版本开发,做这个的程序,会是啥心态
    • 作为管理者,你会悉心了解问题本质,企图改掉它们吗?大概率不会
    • 完整解决它的可能性极低,大约会搞成延时删除、标记删除、拷贝遍历之类的
    • 不解决根本问题,下一个不注意的仍会踩坑 …… 避开坑,而不是填坑
  • c++是个很棒的语言,但得重新审视下它做业务开发的性价比
    • 其它语言碰见这bug的印象淡许多
    • c# foreach 遍历删会报容器被修改的异常
    • go for range 删不会报错
    • 带gc的语言,这方面表现好得多。但一样有坑点
      • 比如遍历中途加元素,引发扩容
      • 你还在遍历的,仍是老的内存块,老的元素
【网友讨论】
  • 定时器,最优方案是,遍历实际只是先选出待执行的callback,收集
    • 遍历收集结束,再单独执行那一小批
    • 一边遍历一边执行,显然是不对的
原文地址:https://www.cnblogs.com/3workman/p/14872179.html