WaitableEvent 跨线程等待的死锁陷阱

场景还原

代码(Uninitialize 中的窗口销毁路径):

复制代码
if (workerWindow_) {
    if (content::GetUIThreadTaskRunner({})->RunsTasksInCurrentSequence()) {
        // 已在 UI 线程,直接销毁
        DestroyWindowOnUIThread();
    } else {
        // 不在 UI 线程,把销毁派发到 UI 线程,然后等待
        base::WaitableEvent window_destroyed(
            base::WaitableEvent::ResetPolicy::MANUAL,
            base::WaitableEvent::InitialState::NOT_SIGNALED);

        content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
            base::BindOnce([](TaskbarHookManager* self, base::WaitableEvent* done) {
                self->DestroyWindowOnUIThread();
                done->Signal();
            }, base::Unretained(this), base::Unretained(&window_destroyed))
        );

        window_destroyed.Wait();  // ← 当前线程阻塞,等待 UI 线程执行完毕
    }
}

WaitableEvent::Wait() 的安全使用前提

WaitableEvent::Wait()裸阻塞,没有消息循环,调用时当前线程完全挂起,直到事件被 Signal。

安全条件

  1. 当前线程没有消息泵(不是 UI 线程、STA COM 线程、任何需要处理消息的线程)
  2. 等待的任务不依赖当前线程或当前线程上的任何资源(无循环依赖)
  3. 等待的任务保证一定会 Signal(无死路)

这里的潜在问题

情形 1:当前线程是 file 线程,UI 线程正常,看起来安全

复制代码
file 线程: window_destroyed.Wait()  ← 阻塞
                                          ↓
UI 线程:   DestroyWindowOnUIThread() → done->Signal()
                                          ↓
file 线程: 继续执行

表面上无死锁,但存在以下隐患:

隐患 A:UI 线程可能已经在等待 file 线程

如果 Uninitialize 本身是被 UI 线程发起的(CleanupHooksDCHECK_CALLED_ON_VALID_SEQUENCE),而 Uninitialize 的前半段已经在 file 线程上执行了某个阻塞性操作,就形成:

复制代码
UI 线程: 等待 file 线程完成清理(WaitableEvent::Wait)
              ↕
file 线程: 等待 UI 线程执行 DestroyWindow(WaitableEvent::Wait)
              → 死锁

隐患 B:TaskShutdownBehavior::SKIP_ON_SHUTDOWN

本项目的 file_task_runner_ 创建时使用了 SKIP_ON_SHUTDOWN

复制代码
file_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({
    base::MayBlock(),
    base::TaskPriority::USER_VISIBLE,
    base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN  // ← 关闭时跳过未执行的任务
});

在浏览器关闭流程中,如果 PostTask 到 UI 线程的任务因关闭而被丢弃(未执行),done->Signal() 永远不会被调用,window_destroyed.Wait() 永久阻塞,进程无法退出(或被系统强杀)。

隐患 C:UI 线程有消息泵但不是纯 Worker

DestroyWindow 本身会触发 WM_DESTROY 等消息,某些 DestroyWindowOnUIThread 实现可能向其他窗口发 SendMessage 进行同步通知,而那些窗口可能正在等待 file 线程 ------ 同样形成死锁。


WaitableEvent 正确 vs 错误用法对比

场景 推荐做法 原因
纯 Worker 线程等待另一个纯 Worker 线程 WaitableEvent::Wait() 可用 双方都无消息泵,无循环依赖风险
纯 Worker 线程等待 UI 线程 ⚠️ 谨慎,需确认 UI 线程当时空闲且不会反向等待 方向性等待,有潜在循环依赖
UI 线程等待任何其他线程 ❌ 禁止直接 Wait UI 线程有消息泵,阻塞会触发 COM/消息死锁
关闭流程中的等待 ❌ 极其危险,SKIP_ON_SHUTDOWN 会让 Signal 永不发出 进程挂死

安全替代方案

替代 1:链式 PostTask(最推荐,全异步)

不需要等待,让操作自然排队:

复制代码
// file 线程清理完后,链式 PostTask 回 UI 线程销毁窗口
file_task_runner_->PostTask(FROM_HERE,
    base::BindOnce([](TaskbarHookManager* manager) {
        // ... 清理逻辑 ...

        // 完成后链式触发 UI 线程销毁窗口
        content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
            base::BindOnce(&TaskbarHookManager::DestroyWindowOnUIThread,
                           base::Unretained(manager))
        );
    }, base::Unretained(this))
);
// 不等待,立即返回

替代 2:在 UI 线程上用 base::RunLoop(需要同步语义时)

RunLoop::Run() 启动嵌套消息循环,线程不阻塞消息处理,只阻塞"代码继续向下执行":

复制代码
// 只能在 UI 线程使用
base::RunLoop run_loop;
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
    base::BindOnce([](TaskbarHookManager* self, base::OnceClosure quit) {
        self->DestroyWindowOnUIThread();
        std::move(quit).Run();
    }, base::Unretained(this), run_loop.QuitClosure())
);
run_loop.Run();  // ✅ 消息泵继续工作,COM/窗口消息可正常分发

替代 3:委托给 SequenceChecker + 生命周期管理

如果销毁顺序是已知的(比如 Controller 先于 Manager 析构),在 Controller 的析构序列中保证顺序,而不是在运行时用 WaitableEvent 同步。


核心原则

WaitableEvent::Wait() 等同于在当前线程插入一个"停止一切"的路障。

只要等待的目标(直接或间接)需要当前线程参与(处理消息、分发 COM 调用、执行回调),就必然死锁。

替代思路:把"等完成后做什么"变成一个回调,让完成事件去触发后续操作,而不是主动等待。

这就是 Chromium 架构中所有异步操作的基本范式:PostTaskAndReplyOnceClosureRunLoop

相关推荐
乐观勇敢坚强的老彭11 分钟前
信奥C++一维数组笔记
开发语言·c++·笔记
王维同学25 分钟前
[原创][Windows C++]Explorer Shell 扩展、图标覆盖与 COM 服务器定位
c++·windows·注册表
昕光xg28 分钟前
Istio笔记04-基于Jaeger的分布式链路追踪
笔记·分布式·istio
学计算机的计算基1 小时前
操作系统内存管理全解:虚拟内存、页表、COW、malloc、OOM一篇搞定
java·笔记·算法
AOwhisky1 小时前
Python 学习笔记(第五期)——组合数据类型:列表、元组、集合与字典精讲——核心知识点自测与详解
开发语言·笔记·python·学习·云计算
砚凝霜1 小时前
软考网络工程师|第 2 章 信道延迟、传输介质、数据编码、数字调制、PCM 完整备考笔记
网络·笔记·pcm
广州灵眸科技有限公司11 小时前
xfce桌面触摸校准:基于灵眸科技EASY-EAl-Orin-Nano
数据库·windows·科技
遇乐的果园14 小时前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
遇乐的果园15 小时前
前端学习笔记-vue状态管理优化
前端·笔记·学习
Ctrl+Z侠16 小时前
.NET WebApi Windows / Linux Docker 全链路压测与瓶颈定位 0 到 1 教程
linux·windows·.net