无锁内存回收:hazard pointer 与 epoch-based reclamation 原理

C++高并发编程:Hazard Pointer机制实现无锁队列内存安全回收 在并发编程中,内存管理是确保程序稳定性的核心挑战,尤其涉及多线程共享数据时。传统锁机制虽能保证数据安全,但锁竞争会显著降低高并发场景下的系统吞吐量。无锁编程通过原子操作实现线程间协作,避免了锁带来的阻塞,从而提升性能。然而,无锁数据结构面临ABA问题和内存回收难题——当线程释放内存时,其他线程可能仍持有该内存指针,导致未定义行为。Hazard Pointer(风险指针)机制通过为每个线程提供“风险登记处”,线程在访问共享指针前先声明保护,只有所有线程都未使用的内存才被安全回收。该技术广泛应用于高性能任务调度 阅读详情

无锁数据结构里最反直觉的一件事:你从无锁栈 pop() 拿到的节点,不能随手 delete 掉。因为你没法保证没有别的线程此刻正握着这个节点的指针、马上就要解引用它。这一篇讲清楚无锁环境下的内存回收为什么难、有哪三种主流思路,每种都给一个能跑的简化实现,最后落到一句话:生产级直接上现成库。

1. 引子:pop 出来的节点凭什么不能 delete

先看一个「看起来正确、实则危险」的无锁栈骨架。它的 push/pop 用 CAS 维护头指针,正确性没问题,但 pop() 里那句 delete 是颗雷:

// 反例,不要这么写:pop 后直接 delete 节点,别的线程可能还在用它
template <typename T>
struct UnsafeStack {
    struct Node {
        T value;
        Node* next;              // 反例:裸指针,只为演示内存回收问题
    };
    std::atomic<Node*> head_{nullptr};

    void push(T v) {
        Node* n = new Node{v, head_.load()};
        while (!head_.compare_exchange_weak(n->next, n)) { }
    }
    bool pop(T& out) {
        Node* old = head_.load();
        while (old && !head_.compare_exchange_weak(old, old->next)) { }
        if (!old) return false;
        out = old->value;
        delete old;              // ← 危险:old 可能正被另一个线程读取
        return true;
    }
};

危险来自下面这种交错:

   线程 A 和线程 B 同时操作无锁栈,A pop 出的节点被 B 读取

   时间 ──────────────────────────────────────────────────────────►
   线程 A      load head → old(=N1) ── CAS 成功,head 指向 N2 ── delete N1 ──►
   线程 B                     load head → 拿到 N1 的指针 ── 解引用 N1->value ──►  ✗ use-after-free

   问题:B 在「load 到 N1」之后、「解引用」之前,A 已经 delete 了 N1。
         所以「节点不在结构里了」≠「节点没人在用了」。

这就是无锁内存回收的核心矛盾:结构上的移除(逻辑删除)和物理上的释放(delete)之间,隔着一段「可能还有人引用」的窗口。有锁的世界里,锁天然保证了「拿到锁 = 别人进不来 = 没人再用」,所以 delete 是安全的;无锁的世界里没有这层保护,必须自己造一套机制。

2. 三种回收思路总览

解决这个矛盾,工业界有三条主流路线,本质都是「延迟释放 + 判定无人引用」:

方案核心思想读侧开销适用场景
引用计数每个节点带引用计数,没人引用才释放一次原子自增/自减通用,实现简单,开销略高
hazard pointer读者声明「我正在用这几个指针」,回收者只释放没人声明的一次原子写(声明)读多写少,节点数有限
epoch-based reclamation读者只登记自己「处于第几个 epoch」,回收者等所有读者都走过静止点再释放极低(一次 store)读侧极频繁、对延迟最敏感(RCU 思路)

下面逐个给简化实现。先声明:这三段的代码都是「讲原理」的最小实现,管理结构里有意用了锁来简化正确性;真正的无锁内存回收器(folly::hazptr、libcds、Linux 内核的 RCU)把「声明」「退休」「回收」全做成了无锁,并处理了 ABA、内存序等细节,工程里不要自己重造。

3. 方案一:引用计数(shared_ptr + 原子操作)

最简单也最通用的办法:让节点自带引用计数,谁拿到指针谁 +1,用完 -1,归零自动释放。C++ 里现成就有 std::shared_ptr,而 std::atomic_load / std::atomic_store / std::atomic_compare_exchange_weak 对 std::shared_ptr 的重载(<memory> 提供,C++11 起就有)能让我们把它塞进无锁结构里,还不产生 data race:

// rc_stack.cpp — 编译: g++ -std=c++17 -Wall -O2 -pthread rc_stack.cpp -o rc_stack
#include <atomic>
#include <cstdio>
#include <memory>
#include <thread>
#include <vector>

template <typename T>
class RcStack {
    struct Node {
        T value;
        std::shared_ptr<Node> next;
    };
    std::shared_ptr<Node> head_;   // 头节点,shared_ptr 自动管引用计数

public:
    void push(T v) {
        auto n = std::make_shared<Node>();
        n->value = v;
        n->next = std::atomic_load(&head_);
        while (!std::atomic_compare_exchange_weak(&head_, &n->next, n)) { }
    }
    bool pop(T& out) {
        auto old = std::atomic_load(&head_);
        while (old && !std::atomic_compare_exchange_weak(&head_, &old, old->next)) { }
        if (!old) return false;
        out = old->value;          // old 是 shared_ptr,此刻引用计数 ≥1,节点不会被释放
        return true;
    }
};

int main() {
    RcStack<int> s;
    std::vector<std::thread> ws;
    for (int t = 0; t < 4; ++t) {
        ws.emplace_back([&s, t] {
            for (int k = 0; k < 2500; ++k) s.push(t * 2500 + k);
        });
    }
    for (auto& w : ws) w.join();

    int got = 0;
    int v;
    while (s.pop(v)) ++got;
    std::printf("pushed=10000 popped=%d\n", got);
}
pushed=10000 popped=10000

关键在 pop() 里的局部变量 old 是 shared_ptr:CAS 失败也好、成功也罢,只要 old 这个局部 shared_ptr 还活着,它指向的节点引用计数就 ≥1,别的线程无论怎么折腾都 delete 不掉它。等 pop 返回、old 析构,引用计数归零,节点才被自动释放。这就是「用引用计数把 use-after-free 堵死」。引用计数就是最朴素的「延迟释放 + 无人引用判定」。

用 ThreadSanitizer(clang 19)复验一遍,确认这套共享指针的原子操作没有 data race:

$ clang++ -std=c++17 -O1 -fsanitize=thread -g -pthread rc_stack.cpp -o rc_tsan && ./rc_tsan
pushed=10000 popped=10000
(stderr 无任何 ThreadSanitizer 报告 —— 说明没有 data race)

代价也摆在那里:每次 push/pop 都伴随一次原子引用计数的加减(shared_ptr 的控制块操作),在高竞争下比纯裸指针慢。所以它适合「正确性优先、竞争不极端」的场景。

官方文档:std::atomic_load / atomic_store / atomic_compare_exchange(shared_ptr 重载) · std::shared_ptr

4. 方案二:hazard pointer(声明「我在用」)

引用计数每次访问都要动引用计数,读侧不便宜。hazard pointer 换个思路:读者在读之前,把「我正握着的指针」写进一个全局可见的 hazard 槽;回收者要释放某个节点前,先扫一遍所有 hazard 槽,只有没人声明的节点才 delete。读侧只需一次 store,回收的代价被摊到回收者身上。

下面是简化实现,hazard 槽用原子变量,退休列表用锁保护:

// hazptr_demo.cpp — 编译: g++ -std=c++17 -Wall -O2 -pthread hazptr_demo.cpp -o hazptr
#include <atomic>
#include <cstdio>
#include <mutex>
#include <vector>

// 简化 hazard pointer:hazard 槽是原子的,退休列表用锁保护
// (生产级如 folly::hazptr 两者都无锁;这里用锁只为把机制讲清楚)
class MiniHazptr {
public:
    static constexpr int kReaders = 2;
    std::atomic<int*> slots_[kReaders]{};   // 每个读者一个 hazard 槽
    std::atomic<int> reclaimed_{0};

private:
    std::mutex mu_;
    std::vector<int*> retired_;

public:
    void acquire(int reader, int* p) { slots_[reader].store(p); }
    void release(int reader) { slots_[reader].store(nullptr); }

    void retire(int* p) {
        std::lock_guard<std::mutex> lock(mu_);
        retired_.push_back(p);
    }

    bool is_protected(int* p) const {
        for (int r = 0; r < kReaders; ++r) {
            if (slots_[r].load() == p) return true;
        }
        return false;
    }

    void reclaim() {
        std::lock_guard<std::mutex> lock(mu_);
        for (auto it = retired_.begin(); it != retired_.end();) {
            if (is_protected(*it)) {
                ++it;                       // 有人正在用,跳过
            } else {
                delete *it;                 // 演示内存回收机制,此处故意裸 delete
                reclaimed_.fetch_add(1);
                it = retired_.erase(it);
            }
        }
    }
};

int main() {
    MiniHazptr hp;
    int* a = new int(1);   // 演示内存回收机制,故意裸 new(真实代码交给现成库)
    int* b = new int(2);

    hp.acquire(0, a);          // 读者 0 声明「正在用 a」
    hp.retire(a);              // 回收者退休 a
    hp.retire(b);              // 退休 b
    hp.reclaim();              // a 被保护不删,只删 b
    std::printf("第一次 reclaim:回收 %d 个(b),a 仍在用=%d\n",
                hp.reclaimed_.load(), *a);

    hp.release(0);             // 读者 0 用完 a
    hp.reclaim();              // 现在 a 才被删
    std::printf("第二次 reclaim:累计回收 %d 个\n", hp.reclaimed_.load());
}
第一次 reclaim:回收 1 个(b),a 仍在用=1
第二次 reclaim:累计回收 2 个

两次 reclaim() 的结果正好说明机制:第一次 a 在 hazard 槽里、被跳过;等读者 release 之后,a 才被回收。时序是这样的:

   hazard pointer 的「声明 → 退休 → 回收」时序

   读者 0      acquire(a) ──────────── release(a) ─────────────────►
   回收者                 retire(a) ── reclaim()          reclaim() ──►
                                      │(扫 hazard:      │(a 已不在
                                      │  槽里有 a,跳过)  │  任何槽里)
                                      ▼                    ▼
                                   a 保留不删            delete a
   b 从未被声明 → 第一次 reclaim 就直接 delete b

hazard pointer 的局限也很明确:读者必须显式、成对地 acquire/release,漏写一次就是 use-after-free;而且一个读者同时最多能用多少个节点,就得准备多少个 hazard 槽。这些正是生产库要替你兜住的地方。

官方文档:std::atomic::store(hazard 槽的原子写) · C++ Core Guidelines CP(并发章节)

5. 方案三:epoch-based reclamation(读侧免锁)

hazard pointer 要求读者「精确声明每个指针」,epoch-based 更粗放:读者只登记「我正处于第几个 epoch」,用完清零;回收者想释放某批节点时,先把全局 epoch 推进一位,然后确认所有读者都已经走过旧的 epoch(静止点,quiescent state),这时退休于旧 epoch 的节点就能安全释放。读侧只是两次 store,几乎没有竞争 —— 这正是 Linux 内核 RCU(read-copy-update)读侧免锁的简化版思路。

// epoch_demo.cpp — 编译: g++ -std=c++17 -Wall -O2 -pthread epoch_demo.cpp -o epoch
#include <atomic>
#include <cstdio>
#include <mutex>
#include <vector>

// 简化 epoch-based reclamation(QSBR 风格):读侧只登记 epoch,免锁
class MiniEpoch {
public:
    static constexpr int kReaders = 2;
    std::atomic<unsigned long> global_epoch_{1};
    std::atomic<unsigned long> reader_epoch_[kReaders]{};   // 0 = 空闲
    std::atomic<int> reclaimed_{0};

private:
    std::mutex mu_;
    std::vector<int*> retired_;

public:
    void enter(int reader) { reader_epoch_[reader].store(global_epoch_.load()); }
    void exit(int reader) { reader_epoch_[reader].store(0); }

    bool all_idle() const {
        for (int r = 0; r < kReaders; ++r) {
            if (reader_epoch_[r].load() != 0) return false;
        }
        return true;
    }

    void retire(int* p) {
        std::lock_guard<std::mutex> lock(mu_);
        retired_.push_back(p);
    }

    // 回收者:等所有读者穿过静止点,推进 epoch,再释放旧 epoch 退休的节点
    void advance_and_reclaim() {
        while (!all_idle()) { }              // 简化:忙等静止点
        const unsigned long old = global_epoch_.load();
        global_epoch_.store(old + 1);        // 推进 epoch
        std::lock_guard<std::mutex> lock(mu_);
        for (auto it = retired_.begin(); it != retired_.end();) {
            delete *it;                       // 演示内存回收机制,此处故意裸 delete
            reclaimed_.fetch_add(1);
            it = retired_.erase(it);
        }
    }
};

int main() {
    MiniEpoch e;
    int* a = new int(1);   // 演示内存回收机制,故意裸 new

    e.enter(0);                        // 读者 0 进入读临界区
    e.retire(a);                       // 退休 a
    std::printf("读者在读时能回收吗 = %d\n", static_cast<int>(e.all_idle()));

    e.exit(0);                         // 读者 0 退出,到达静止点
    e.advance_and_reclaim();           // 推进 epoch 并回收
    std::printf("读者退出后累计回收 = %d,新 epoch = %lu\n",
                e.reclaimed_.load(), e.global_epoch_.load());
}
读者在读时能回收吗 = 0
读者退出后累计回收 = 1,新 epoch = 2

注意 enter/exit 只是两次对原子变量的 store,读者之间互不干扰 —— 这就是「读侧免锁」的含义。代价转移到回收者:它必须等所有读者都走到静止点,任何一个读者卡在临界区里,回收就整体被拖住。所以 epoch-based 适合「读极多、写极少、读者绝不在临界区里阻塞」的场景(比如路由表、配置的热更新)。

6. 完整示例:引用计数的多生产者多消费者无锁栈

把最实用的引用计数方案做成完整版:4 个生产者各 push 一批,4 个消费者并发 pop,用「总和守恒」验证每个值恰好被取走一次、没有丢也没有重复:

// rc_stack_full.cpp — 编译: g++ -std=c++17 -Wall -O2 -pthread rc_stack_full.cpp -o rcfull
#include <atomic>
#include <cstdio>
#include <memory>
#include <thread>
#include <vector>

template <typename T>
class RcStack {
    struct Node {
        T value;
        std::shared_ptr<Node> next;
    };
    std::shared_ptr<Node> head_;

public:
    void push(T v) {
        auto n = std::make_shared<Node>();
        n->value = v;
        n->next = std::atomic_load(&head_);
        while (!std::atomic_compare_exchange_weak(&head_, &n->next, n)) { }
    }
    bool pop(T& out) {
        auto old = std::atomic_load(&head_);
        while (old && !std::atomic_compare_exchange_weak(&head_, &old, old->next)) { }
        if (!old) return false;
        out = old->value;
        return true;
    }
};

int main() {
    constexpr int kProducers = 4;
    constexpr int kConsumers = 4;
    constexpr int kPerProducer = 2500;
    constexpr int kTotal = kProducers * kPerProducer;

    RcStack<int> s;

    std::vector<std::thread> producers;
    for (int p = 0; p < kProducers; ++p) {
        producers.emplace_back([&s] {
            for (int k = 1; k <= kPerProducer; ++k) s.push(k);   // 每个生产者 push 1..2500
        });
    }
    for (auto& t : producers) t.join();

    std::atomic<long long> sum{0};
    std::atomic<int> count{0};
    std::vector<std::thread> consumers;
    for (int c = 0; c < kConsumers; ++c) {
        consumers.emplace_back([&s, &sum, &count] {
            int v;
            while (s.pop(v)) {
                sum.fetch_add(v, std::memory_order_relaxed);
                count.fetch_add(1, std::memory_order_relaxed);
            }
        });
    }
    for (auto& t : consumers) t.join();

    const long long expected = static_cast<long long>(kProducers) * kPerProducer * (kPerProducer + 1) / 2;
    std::printf("pop 总数 = %d(期望 %d)\n", count.load(), kTotal);
    std::printf("pop 总和 = %lld(期望 %lld)\n", sum.load(), expected);
}
pop 总数 = 10000(期望 10000)
pop 总和 = 12505000(期望 12505000)

两个数字都精确对得上,说明在引用计数保护下,多生产者多消费者各自并发地 CAS 头指针,没有丢节点、没有重复取、也没有一次 use-after-free。这正是引用计数作为「最省心的内存回收」的底气。工程上,std::atomic<std::shared_ptr<T>> 在 C++20 里把上面那三个自由函数收编成了成员函数,写起来更顺手;但在 C++17 环境下,自由函数版本就是标准写法。

7. 延伸阅读

本知识库内的相关篇目:

8. 一句话总结

无锁结构里「从结构移除节点」和「物理 delete 节点」之间隔着一段无人知道的引用窗口,所以不能直接 delete;三种回收思路本质都是「延迟释放 + 判定无人引用」。引用计数靠 shared_ptr 最省心、hazard pointer 靠读者声明「我在用」省读侧开销、epoch-based 靠读者登记 epoch 做到读侧免锁;这三段的代码都是讲原理的最小实现,生产级请直接用 folly::hazptr、libcds 或内核 RCU 这类经过验证的现成库,别自己重造。

无锁队列内存回收利器:EBR核心原理与实现 无锁队列在高并发场景下常因节点释放时机不当引发use-after-free和ABA问题,内存回收成为无锁编程的关键难点。EBR(基于时代的内存回收)通过全局时代与线程本地时代的协同,将退役节点延迟到安全时机批量回收,读路径开销极低,特别适合MPMC队列等高频操作场景。相比Hazard Pointer,EBR以稍高的内存占用换取更高吞吐,但需警惕观察者饥饿问题。本文从内存回收的通用概念出发,详细拆解EBR的三大核心结构、状态流转规则及其与MPMC队列的整合流程,并给出可直接落地的C++实现思路、阈值调优与实战 阅读详情

相关推荐

C++内存模型与CAS:无锁编程的核心原理与实战应用

并发编程是现代软件开发的基础,它允许多个任务同时执行以提升系统性能。其核心原理在于管理共享资源的访问顺序与可见性,避免数据竞争与状态不一致。C++11引入的内存模型为此提供了标准化的解决方案,通过定义原子操作与内存顺序(memory_order),在编译器优化与硬件乱序执行之间建立契约,确保多线程环境下的行为可预测。这一技术的核心价值在于实现高性能、低延迟的无锁(lock-free)数据结构,如队列、栈与计数器,从而避免传统互斥锁带来的上下文切换开销与死锁风险。其中,比较并交换(CAS)操作是无锁编程的原子

weixin_33794672的博客 429

Go/Rust 系统编程:无锁数据结构与内存序实战指南

无锁数据结构通过原子操作和内存序避免锁竞争,在极端高并发场景下提供数量级的性能提升。内存序是无锁编程的核心——Release-Acquire 是最常用的组合,它在 x86 上几乎零开销,在 ARM 上需要适当的内存屏障。无锁编程的代价是 ABA 问题和内存回收困境,需要 Tagged Pointer 和 Hazard Pointer/EBR 等机制来解决。建议仅在锁竞争成为明确瓶颈时才引入无锁方案,优先使用成熟的库(如 crossbeam、disruptor)而非自行实现。

iymei4986533030的博客 4871

C++无锁编程性能陷阱剖析与系统化优化实践

并发编程是构建高性能系统的核心技术,其核心挑战在于如何安全高效地协调多线程对共享资源的访问。传统基于锁的同步机制虽然直观,但在高竞争场景下可能引发线程阻塞、上下文切换等开销,成为性能瓶颈。无锁编程通过原子操作和内存屏障,避免了显式锁带来的阻塞,理论上能实现更高的吞吐量和更低的延迟。然而,这项技术并非银弹,它将同步的代价从操作系统调度转移到了CPU流水线、缓存一致性和算法复杂性层面。在实践中,开发者常面临伪共享导致的缓存行乒乓、忙等待造成的CPU资源浪费、ABA问题带来的正确性与内存管理挑战,以及对C++内存

weixin_30729609的博客 472

【C++多线程】无锁编程与无锁队列:CAS、ABA 问题与内存回收

主要讲述无锁编程的必要性、定义、CAS循环、无锁栈、ABA问题及解决方案。

Mr.Sugarcane 342

C++内存管理与并发编程深度优化:从RAII到无锁数据结构实战

内存管理与并发编程是高性能C++开发的核心基石。理解内存模型与原子操作是保证多线程程序正确性的基础,它确保了指令重排下的可预测性。RAII(Resource Acquisition Is Initialization)技术通过智能指针(如std::unique_ptr、std::shared_ptr)自动化资源管理,是避免内存泄漏的关键范式。在追求极致性能的场景下,自定义内存分配器能显著减少系统调用开销,而理解缓存一致性(如false sharing)则是优化多核并发的关键。这些技术的价值在于,它们共同支撑

weixin_34072637的博客 377

C++高并发编程:原子操作与无锁队列核心原理与实战

在高并发编程中,线程间的数据同步与通信是核心挑战。传统互斥锁通过阻塞线程来保证数据安全,但会引入上下文切换和等待开销,成为性能瓶颈。原子操作提供了对内存位置进行不可分割“读-改-写”的能力,是构建高效并发原语的基石,其关键在于理解内存序(Memory Order)以控制操作可见性。基于此,无锁数据结构得以实现,它允许多线程并发访问而无需互斥等待,其核心价值在于消除锁竞争带来的延迟与吞吐量下降,尤其适用于高频交易、游戏服务器、实时数据处理等对延迟极其敏感的领域。本文以无锁队列这一典型数据结构为例,深入剖析其实

chubaisheng8627的博客 424

用C++实现无锁队列:从ABA问题到高性能并发结构

无锁队列是高性能并发系统的基石,但其实现复杂度远超表面。CPU内存模型缓存一致性协议编译器/CPU指令重排内存屏障语义在实际项目中,除非性能瓶颈明确指向锁竞争,否则优先选择标准库或成熟第三方实现(如Intel TBB、folly、boost.lockfree)。自己实现无锁结构应作为学习和极致优化手段。结合Rust的所有权模型实现更安全的无锁结构利用eBPF在内核层实现零拷贝无锁队列探索硬件事务内存(HTM)替代软件CAS。

m0_75015600的博客 827

C++无锁队列实现原理与性能优化实战

原子操作是现代并发编程的核心基础,它通过硬件级别的指令保证对共享数据的读写是原子的,从而为构建无锁数据结构提供了可能。其原理基于CPU提供的CAS等原子指令,结合内存顺序模型,确保多线程间的操作可见性与顺序性。这项技术的核心价值在于消除传统互斥锁带来的线程阻塞、上下文切换开销,从而极大提升高并发场景下的吞吐量和降低延迟。它广泛应用于高频交易、游戏服务器、网络数据包处理等对性能要求严苛的领域。本文以C++无锁队列为例,深入剖析其设计哲学,详解如何利用原子操作和内存顺序实现线程安全的入队与出队,并探讨性能优化与

weixin_33859844的博客 447

Python无GIL时代来临:2024 CPython 3.13+ 下3种零竞态并发架构(附内存屏障+RCU实战代码)

Python无GIL环境下实现真正并行,本文提供Python无锁GIL环境下的并发模型安全性最佳方案:基于内存屏障、RCU及原子操作的3种零竞态架构,适用于高性能服务与实时计算场景,兼顾性能与线程安全,值得收藏。

CodeIsle的博客 386

raw_count、tpm、fpkm、rpkm如何选择

转录组测序中常见的数据类型有:raw_count、tpm、fpkm、rpkm。本文进行简单辨析: 一、概念 1 raw_count RNA-seq数据中,raw_count一般是指mapped到基因外显子区域的reads数目。比如说htseq,STAR,或者RSEM等NGS分析流程计算产生的counts值。其中RSEM(RNA-Seq by Expectation-Maximization),考虑到一条read 可能会匹配多个exon位置,故而其产生的为expected_count。 2 TPM

weixin_59289660的博客 1万+

Ruoyi-Cloud集成Seata2.0实现分布式事务

在网络上,关于Spring Cloud和Ruoyi-Cloud整合Seata的教程有很多,针对不同版本的 Seata 也有不少相关教程。事实上,无论是 Spring Cloud 还是 Ruoyi-Cloud,其整合 Seata 的原理基本相同。这篇博文将详细介绍如何在本地开发环境下,在中实现Seata 2.0的整合,希望能为大家在分布式项目中实现 Seata 的整合提供实用的帮助,提供一个清晰的操作思路。

SR003的博客 2245

深入xenium并发哈希表:harris_michael_hash_map与vyukov_hash_map实现原理完整对比

xenium 是一个开源 C++ 头文件并发数据结构库,内置两种设计思路截然不同的并发哈希表:无锁的 `harris_michael_hash_map` 与细粒度桶锁的 `vyukov_hash_map`。本文带你快速读懂两者的实现原理、性能特征与选型方法。 ## 两种并发哈希表,一句话看懂差异 - 🔓 **harris_michael_hash_map**:完全无锁(lock-free),

gitblog_00373的博客 1037

DeepSeek+Dify实战:零代码构建智能SQL生成器

本文详细介绍了如何利用DeepSeek和Dify零代码构建智能SQL生成器,帮助非技术人员通过自然语言查询数据库。文章涵盖从数据准备、知识库设计到Agent配置的全流程,并提供了优化技巧和实际应用案例,显著提升团队数据查询效率。

weixin_29311017的博客 416

无锁编程与原子操作:高并发系统的性能优化实践

原子操作是并发编程中的基础概念,指不可分割的指令执行单元,现代CPU通过硬件指令如CMPXCHG实现。其核心价值在于避免锁竞争带来的线程阻塞,特别适用于多核处理器环境。无锁编程(Lock-Free Programming)基于原子操作构建,通过CAS(比较并交换)等机制实现线程安全,在金融交易系统、游戏服务器等低延迟场景表现优异。典型实现包括无锁队列、环形缓冲区等数据结构,需注意ABA问题、内存屏障使用等关键点。C++/Java等语言均提供标准库支持,结合缓存行对齐、批量操作等优化技巧,可显著提升高并发系统

weixin_34335458的博客 408

使用 EasyExcel 实现 百万级数据导入导出

在项目开发中往往需要使用到数据的导入和导出,导入就是从Excel中导入到DB中,而导出就是从DB中查询数据然后使用POI写到Excel上。大数据的导入和导出,相信大家在日常的开发、面试中都会遇到。很多问题只要这一次解决了,总给复盘记录,后期遇到同样的问题就好解决了。好啦,废话不多说开始正文!1.如此大批量数据的导出和导入操作,会占用大量的内存实际开发中还应限制操作人数。2.在做大批量的数据导入时,可以使用jdbc手动开启事务,批量提交。

houxian1103的博客 2699

【SigmaStudio】自定义算法组件之源码规范(二)

工作中自定义算法组件一般是dll文件,生成dll文件需要XML文件和DLB文件,记录一下DLB文件代码的编码规范,定义SigmaStudio SHARC算法开发的编码规则,涵盖函数原型、内存管理、接口结构体等关键要求,确保算法模块与SigmaStudio环境兼容

liang_song的博客 1377

深入理解无锁编程:从CAS原理到ABA问题解决方案

并发编程中,锁机制虽然保证了数据安全,但在高竞争场景下可能引发性能瓶颈。无锁编程通过原子指令(如CAS)实现非阻塞同步,提升系统吞吐量。其核心原理是利用硬件支持的原子操作,让线程通过尝试-失败-重试循环更新共享数据,避免线程挂起开销。然而,无锁设计面临ABA问题的经典挑战——即指针地址复用导致的数据一致性问题。解决ABA问题需引入版本号或标签指针等机制,确保CAS检查值的唯一性。在C++工程实践中,使用std::shared_ptr的引用计数可天然防止内存复用,是兼顾安全性与开发效率的实用方案。无锁数据结构

weixin_34006468的博客 302

【C2000系列DSP的Bootloader详解】如何利用脚本自动合并Bin文件

本文通过OutconvertBin.py实现了 C2000 Bootloader+UserApp 的「ELF 转 BIN+BIN 合并」全流程自动化,核心优势如下: 架构适配:严格遵循 C28x 16 位大端格式,生成的 BIN 文件可直接烧录; 自动化程度高:一键完成清理、转换、合并,无需手动执行多个命令; 可追溯性:生成 CRC32 校验值,便于验证文件完整性; 量产友好:合并后的merged.bin支持一次性烧录,提升量产效率。

昔时扬尘处 182

无锁队列原理与实现:从CAS到MPMC环形队列的完整指南

并发编程中,队列是数据交换与解耦的核心组件。当锁竞争成为性能瓶颈,无锁队列通过CAS原子操作与内存序机制,让线程在无需阻塞的前提下安全协作,从而显著降低尾部延迟。其核心设计围绕原子读改写、内存序配对(release/acquire)以及ABA问题的规避展开,实践中常采用带序列号的有界环形数组实现多生产多消费(MPMC)队列,并通过缓存行对齐、批量操作等手段进一步优化。链表方案则需借助Hazard Pointer或Epoch机制处理内存回收问题。这种技术广泛适用于日志管道、事件分发、网络缓冲等对延迟稳定性要求

weixin_32430941的博客 326

MIPI D-PHY TX 一致性测试实例解析 Part 01 续

本文为MIPI D-PHY TX一致性测试实例解析part01章节的第二部分

一只豌豆象的博客 6041

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

C++无锁编程7大核心技巧:从原子操作到高性能数据结构实战

原子操作是现代并发编程的基石,它通过CPU指令级的支持,确保了多线程环境下对共享数据的不可分割访问。其核心原理在于利用硬件提供的原子指令(如CAS),结合编译器屏障和内存顺序模型,在无锁条件下实现线程间的安全同步。这项技术的核心价值在于彻底消除传统互斥锁带来的线程阻塞、上下文切换和优先级反转开销,从而在高度竞争的场景下实现数量级的吞吐量提升和延迟降低。它尤其适用于对性能有极端要求的应用场景,如高频交易系统、实时游戏服务器、大型数据库引擎和通信中间件。本文将以C++无锁编程为例,深入解析其7大实现技巧,涵盖C

cnracht8153的博客 388

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C++并发编程实战:从线程管理到无锁数据结构与性能调优

并发编程是现代软件开发的核心技术,它允许多个计算任务同时执行,从而充分利用多核处理器的硬件潜力,提升程序性能。其基本原理在于通过线程、进程等机制实现任务并行,并借助同步原语(如互斥锁、条件变量)和内存模型来协调共享资源的访问,确保数据一致性与程序正确性。这项技术的核心价值在于解决高并发场景下的性能瓶颈,是构建高性能服务器、实时系统和大规模数据处理应用的基础。在实际工程中,开发者常面临数据竞争、死锁和伪共享等挑战,需要掌握从基础的std::thread管理、RAII模式,到高级的原子操作、内存序以及无锁数据结

weixin_30458043的博客 393

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

C++高性能栈设计:从基础实现到内存池与并发优化实战

栈(Stack)作为一种后进先出(LIFO)的基础数据结构,其核心原理在于通过连续的压栈(push)和弹栈(pop)操作管理数据。在追求极致性能的C++工程实践中,简单的标准库实现常成为瓶颈。深入其技术价值,高性能栈设计需在内存布局、并发安全与异常保证间精妙权衡,尤其在高频交易、实时系统等场景下,性能直接决定系统稳定性。本文聚焦【内存池】优化与【无锁栈】实现,剖析如何通过自定义内存管理避免频繁分配释放带来的内存碎片,并探讨无锁编程中经典的ABA问题及其解决方案,为构建健壮的高性能C++底层组件提供实战指引。

weixin_34138056的博客 428

锁优化与无锁编程:从锁竞争到CAS的并发性能进阶指南

并发编程中,锁竞争会引发上下文切换、缓存一致性开销与串行化瓶颈,直接限制系统吞吐。理解锁的代价后,可通过减小锁粒度、锁分离、缩短持锁时间等策略优化;当业务仍聚焦在少量热点数据上时,CAS、volatile与内存屏障构成了无锁编程的基石,以原子操作替代阻塞,在无锁队列和计数场景下消除锁竞争。无锁并非银弹,自旋开销、ABA问题与代码复杂性需要在工程中仔细权衡。本文面向高并发服务、缓存、分布式系统等场景,系统阐述从锁优化到无锁方案的技术演进路径,帮助开发者突破并发性能瓶颈。

weixin_33860553的博客 292

粗讲内存模型

c++ 内存模型

gwl2115的博客 303
上一篇: 异常的实现与代价:零成本异常、栈展开与 -fno-exceptions 什么时候用
下一篇: SIMD 与自动向量化:march=native、__restrict 与 likely
无名猿
博客等级 码龄10年 61粉丝 184原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值