- 自我介绍
您好,我叫江岸伟,目前是安徽大学计算机技术专业的硕士研究生,预计2027年毕业。我主要关注C++客户端开发,也有智能汽车和AI Agent项目经验。
在腾讯搜狗输入法PC客户端实习期间,我做了资源占用监控、UI卡顿检测和流畅性优化。通过Watchdog探测和调用栈分析,定位到UI热路径获取跨进程互斥锁时无限等待的问题,随后新增兼容现有ABI的超时锁接口,并设计焦点驱动的线程本地配置快照,使按键路径加锁次数下降50%。此前在科大讯飞,我主要参与车位管理、泊车状态流程和通道占用判定。
项目方面,我基于LangGraph开发了游戏Agent,用三个专家Agent协作,并通过MCP完成逐动作执行和状态校验。我希望继续做客户端相关工作,把问题定位、多线程编程和性能优化方面的经验用在更复杂的业务场景中。
- 你这个UI卡顿检测,包括监控,是本来就有的一些基建吗?
这里需要区分我负责的客户端监控链路和公司级基础设施。我能确认的工作是:搭建覆盖五个核心进程的资源占用监控链路,完成CPU、内存、磁盘空间及磁盘I/O的采集、聚合和上报,支持策略云控,并接入实验版本。
UI卡顿检测方面,我设计了Watchdog线程,每200毫秒通过PostMessage发送异步探测,由UI线程更新原子ACK时间戳,再按响应延迟进行三级判定,卡顿时采集调用栈。这部分是我可以具体解释设计和定位过程的内容,而PostMessage等属于Windows已有的系统能力。
至于上传通道、服务端存储、看板和告警分别复用了哪些已有平台,是否由其他同事负责,需要根据实际接入情况进一步核对。我不会把自己负责的客户端采集和检测模块,扩大成独立建设了整套监控基础设施。
- 卡顿检测的归因是自动归因吗
我会把自动检测和自动归因分开说。能确认的是,Watchdog会自动探测UI响应、按延迟分级,并在卡顿时采集调用栈;但采到了栈,不代表系统已经自动找到了根因。目前材料不足以确认是否实现了自动分类或完整的自动归因。
实际定位到的问题是UI热路径频繁获取缺少超时机制的跨进程互斥锁,可能导致输入法及宿主应用卡死。解释这个案例时,重点应当是从等待位置和相关代码分析出锁竞争问题,而不能把一次成功定位,包装成平台已经可以自动判定所有卡顿原因。
建议回答(方案层面,需按实际实现核对):可以先按等待函数、热点栈和重复样本做规则分类,输出疑似锁等待、I/O或计算热点,再结合线程关系和运行指标给出证据与置信度。自动分类帮助缩小排查范围,最终根因仍需要复现或修复验证。
- 为什么不能自动归因,困难在哪
我不会说卡顿完全不能自动归因,而是自动发现异常、自动聚类和证明根因,是三个不同层次。调用栈只记录某一时刻线程停在哪里,比如停在锁等待上,并不能直接说明谁持有锁、为什么没有释放,也不能区分持锁方工作太慢和真正的死锁。
跨进程场景更难,因为证据可能分散在输入法、宿主进程和系统调度里。单次采样还可能抓到偶然状态;采样延迟、符号缺失、版本差异及其他线程的并发活动,都可能导致误判。即使CPU较高,也不能仅凭同时出现就认定它是UI卡顿的原因。
建议回答(方案层面,需按实际实现核对):我会先做有证据的辅助归因,结合多次堆栈、锁等待关系、CPU和I/O指标以及事件时间线,给出候选原因与置信度;证据不足就保留未知。再通过复现、对照实验和修复后的指标变化验证因果关系,而不是强行给每次卡顿贴确定标签。
- 为什么用PostMessage
Watchdog 本身不能成为新的卡顿源,所以它的探测逻辑要尽量轻量、异步,而且不能依赖被监控的 UI 线程及时响应。使用
PostMessage后,Watchdog 只负责投递探测消息,不同步等待 UI 处理;如果 UI 真卡住了,Watchdog 仍然能继续运行、做超时判定和后续采栈。
- 三级判定的阈值都是多少
200, 500, 3000
- 暂停UI线程会有一些风险,怎么避免呢
暂停 UI 线程有一个风险,就是它可能恰好停在持锁区间。如果这时候其他工作线程继续申请同一把锁,就会被一起阻塞,甚至形成级联卡顿。
所以 Suspend 的时间一定要非常短,只用于快速获取线程上下文和必要的栈信息,然后马上 Resume,不能在线程暂停期间做符号解析、日志写入、网络上报这类耗时操作。
- 流畅性优化这里,焦点如果不切换,用户修改一些设置,是不是有同步性问题
我们当时的主要优化目标是输入热路径,所以允许部分配置在当前焦点周期内是最终一致,而不是强一致;下一次焦点切换时会同步。如果某些配置要求修改后立即生效,就不能只依赖焦点切换,还是会去同步。
-
这里有没有一些分级,什么同步,什么不同步
-
现在让你来设计一个平台,包括监控什么的,你怎么设计
建议回答(方案层面,需按实际实现核对):我会先明确平台服务于发现异常、辅助定位和验证修复,按客户端采集、传输接入、分析存储、看板告警四层设计。指标覆盖CPU、内存、磁盘及UI响应,按版本、进程和设备环境分组,并明确活跃设备或活跃时长等统计分母。
客户端采用轻量周期采集,异常时有限采栈,同时约束CPU、内存和上报量,支持云控、采样和紧急关闭。上传放到后台,使用有界缓冲、批量发送和带退避的有限重试,允许低优先级数据降级丢弃,避免监控反而造成卡顿。
服务端做鉴权、限流、事件去重和数据校验,保留时间、构建版本与符号标识;指标用于趋势分析,堆栈用于聚类和排查。结合分位数、卡顿率及最小样本量告警,再关联多次堆栈和运行指标提供疑似原因,不承诺全自动根因识别。
最后建立异常到负责人、复现、修复、灰度对照验证的闭环。输入法尤其要避免采集用户输入,堆栈和设备信息也应最小化、脱敏并限制留存。平台自身还要监控采集覆盖、数据丢失和处理延迟。
- 你这个游戏agent,多agent怎么协作的
我的项目基于LangGraph,把战斗、卡组构建和路线规划拆成三个专家Agent。分工的目的,是把当前战斗决策、长期构筑和路线风险分开处理,而不是让一个Agent在每个场景都承担所有任务。当前场景通过确定性路由分派到相应专家。
专家之间通过类型化消息总线传递卡组规划、战斗复盘和路线风险信息。比如卡组规划提供长期方向,战斗复盘反馈当前卡组的表现,路线风险信息辅助后续选择;这样既有职责隔离,又能共享整局游戏所需的信息,协作过程也能够追踪。
执行层通过MCP接入实时游戏状态和操作接口,采用逐动作执行、状态结算校验和语义卡牌定位,处理抽牌、弃牌及复杂选择界面。策略侧按场景加载结构化手册,并结合分层记忆和战斗复盘调整整局策略。至于冲突仲裁、消息字段和优先级的具体实现,需要按代码核对,不能仅由多Agent协作推定已经全部具备。
-
多agent 如果出现冲突,怎么解决
-
讲一下你对面向对象中多态的理解
我的理解是,通过统一接口使用不同对象时,能够得到符合各自类型的行为,调用方依赖抽象而不必到处判断具体类型。以C++为例,典型的运行时多态是基类声明虚函数,派生类重写,通过基类指针或引用调用时,根据对象实际类型选择实现。
比如定义一个决策接口,让不同策略分别实现,调度方只负责调用接口。派生类使用override可以让编译器检查重写是否正确。如果需要通过基类指针删除派生对象,基类析构函数应为虚函数;还要避免按基类值传递造成对象切片,否则派生部分会丢失。
C++也有编译期多态,例如模板;函数重载也在编译期完成选择。运行时多态适合动态替换实现,模板适合类型在编译期已知的场景。虚表和虚指针是常见实现机制,不是语言规定的唯一实现;选择时要权衡耦合、扩展性、间接调用开销和代码体积,而不是为了用继承而用继承。
- 了没了解过一些设计模式呢
有了解。我会从它解决什么变化来理解,而不是只记类图。策略模式封装一组可替换算法,调用方依赖统一接口;观察者模式让发布者通知多个订阅者,减少直接依赖,但要处理订阅生命周期、通知顺序和重入问题;状态模式则把不同状态下的行为和转换约束组织起来。
策略模式和状态模式的结构可能相似,但意图不同:策略强调选择不同算法,状态强调对象行为随内部状态变化。另外,工厂模式集中对象创建逻辑,使使用方不必知道具体构造细节。模式有适用范围,小规模且稳定的逻辑不一定需要额外抽象。
我实际维护过APA状态机,也在游戏项目里做了多Agent路由和类型化消息总线,但不能仅凭这些名称,就说代码一定实现了某种经典模式。建议回答(方案层面,需按实际实现核对):可以用策略模式隔离不同决策实现,用发布订阅组织消息通知;是否采用状态模式,要看状态行为是否复杂到值得拆分类。
- 单例模式有什么缺点,怎么解决呢
单例的主要问题是全局状态和隐式依赖,会导致耦合高、测试困难,还要处理多线程安全和生命周期问题。C++11 可以用函数内静态变量解决初始化线程安全,但内部状态仍然需要额外同步。工程上我更倾向于把“单实例”作为对象组装层的约束,业务层通过依赖注入拿接口,而不是到处直接调用
GetInstance(),这样测试和后续扩展会更容易。
- Https 是可靠的,在哪些环节可能会超时
一次 HTTPS 请求大致会经历 DNS 解析、TCP 建连、TLS 握手、发送 HTTP 请求、服务端处理、接收响应几个阶段,所以相应地会有 DNS timeout、connect timeout、TLS handshake timeout、write timeout、response/first-byte timeout 和 read timeout。HTTPS 基于 TCP 的可靠传输,并不意味着没有超时,可靠性和时延保证是两个不同概念。
- Tcp 是可靠的,为什么有收到重复数据的问题
TCP 层可能会收到重复报文,比如 ACK 丢失后发送方重传,但接收端会根据 Sequence Number 去重,所以重复 TCP Segment 不会直接变成应用层重复数据。
如果应用层看到重复请求或重复消息,更常见的原因是应用层重试、断线重连、超时后重新发送,或者业务自身没有做幂等。
- Static 修饰的类 和 单例模式有什么区别
C++ 本身没有 static class,通常所谓 static 类就是全部使用静态成员。它和单例最大的区别是:static 类没有实例,而单例是真实存在一个对象。因此单例可以保存普通成员状态,可以实现接口、参与多态,也更容易做依赖注入和 Mock。不过如果业务代码到处直接调用
Instance(),它和 static 类一样也会产生全局状态和隐式依赖。无状态工具我更倾向于 static 函数或 namespace,有状态、有生命周期的服务才考虑对象或单例。