You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现侵入式链表版信号槽机制的线程安全?

侵入式链表实现Qt风格信号槽的线程安全解决方案

核心问题拆解

多线程场景下的冲突根源在于两个关键操作的并发:

  • 信号触发时遍历侵入式链表,调用连接节点对应的槽函数
  • 信号/槽实例销毁时,从链表中移除连接节点并销毁该节点

直接用互斥量保护链表无法解决“节点正在被调用时不能销毁,销毁时不能再被调用”的依赖冲突,需要结合原子操作、引用计数等机制来处理。

具体解决方案

1. 连接节点的引用计数+原子失效标记

给每个Node添加原子引用计数器和失效标记,确保节点在被调用期间不会被销毁,销毁操作会等待所有正在进行的调用完成:

  • 初始化ref_count为1(初始持有:信号/槽的链表)
  • 信号触发调用Node::foo()时,先递增引用计数,再检查失效标记:若未失效则执行槽函数,执行完成后递减引用计数;若计数归零则直接销毁节点
  • 信号/槽销毁时,先将节点从链表中移除,再设置失效标记并递减引用计数:若计数归零则直接销毁,否则等待后续调用完成后自动销毁

2. 链表遍历的快照优化

为了减少互斥量持有时间,避免阻塞链表的修改操作:

  • 信号触发时,先在互斥量保护下将链表节点复制到临时快照数组
  • 释放互斥量后遍历快照调用槽函数
  • 快照中的节点通过引用计数保证在调用期间不会被销毁

3. 槽实例的生命周期防护

给槽类B添加原子销毁标记,在槽函数执行前做合法性检查:

  • 槽实例销毁时先设置is_destroyed为true
  • B::bar()执行前先检查该标记,若已销毁则直接返回
  • 该标记需配合引用计数使用,确保正在执行的槽函数完成后再释放槽实例资源

4. 侵入式链表的互斥保护

给信号类A和槽类B的侵入式链表各自添加专属互斥量:

  • 所有链表的添加、移除、快照生成操作必须在互斥量保护下进行
  • 遍历链表调用槽函数时,尽量缩短互斥量持有时间(比如仅在生成快照时持有)

关键代码片段

#include <atomic>
#include <mutex>
#include <vector>

// 简化的侵入式链表节点
class Node {
public:
    std::atomic<int> ref_count{1};
    std::atomic<bool> invalid{false};
    class A* signal;
    class B* slot;

    void trigger() {
        // 检查是否已失效,避免无效调用
        if (invalid.load(std::memory_order_acquire)) return;

        // 递增引用计数,确保当前调用期间节点不会被销毁
        ref_count.fetch_add(1, std::memory_order_acq_rel);
        if (!invalid.load(std::memory_order_acquire)) {
            slot->bar();
        }

        // 递减引用计数,检查是否可以销毁节点
        if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
            delete this;
        }
    }

    void invalidate() {
        invalid.store(true, std::memory_order_release);
        // 初始引用计数为1(链表持有),移除后递减,若归零则销毁
        if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
            delete this;
        }
    }
};

class B {
public:
    std::atomic<bool> is_destroyed{false};
    std::mutex list_mutex;
    IntrusiveList<Node> connections; // 自定义侵入式链表

    ~B() {
        is_destroyed.store(true, std::memory_order_release);
        std::lock_guard<std::mutex> lock(list_mutex);
        
        while (!connections.empty()) {
            Node* node = connections.front();
            connections.pop_front();
            
            // 从信号的链表中移除该节点(信号类需提供互斥保护的移除方法)
            std::lock_guard<std::mutex> sig_lock(node->signal->list_mutex);
            node->signal->connections.remove(node);
            
            node->invalidate();
        }
    }

    void bar() {
        if (is_destroyed.load(std::memory_order_acquire)) return;
        // 槽函数业务逻辑
    }
};

class A {
public:
    std::mutex list_mutex;
    IntrusiveList<Node> connections;

    void emit_signal() {
        std::vector<Node*> snapshot;
        {
            std::lock_guard<std::mutex> lock(list_mutex);
            // 生成链表快照
            for (auto* node : connections) {
                snapshot.push_back(node);
            }
        }

        // 遍历快照触发槽函数,无需持有互斥量
        for (auto* node : snapshot) {
            node->trigger();
        }
    }
};

内容的提问来源于stack exchange,提问作者Patrick Wright

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:45:05