RAII 与智能指

1. RAII 核心思想

RAII(Resource Acquisition Is Initialization)将资源生命周期绑定到对象生命周期

  • 构造函数获取资源(内存、文件句柄、锁、线程句柄等)
  • 析构函数释放资源
    优势:异常安全、减少泄漏、代码更可维护。

典型 RAII 场景:

  • 内存管理:std::unique_ptr / std::shared_ptr
  • 锁管理:std::lock_guard / std::unique_lock / std::scoped_lock
  • 线程管理:自定义 scoped_thread 或 C++20 std::jthread
  • 文件/句柄:std::fstream、封装句柄类等

2. 进程与线程(快速澄清)

  • 进程:独立地址空间与资源(虚拟内存、句柄表等)。
  • 线程:同一进程内共享地址空间(堆、全局变量),各自有独立栈与寄存器上下文。
  • 多线程核心风险:数据竞争(data race)死锁(deadlock)活锁/饥饿(livelock/starvation)

3. std::thread 基础与“线程所有权”

3.1 创建线程、join/detach、joinable

#include <thread>
#include <iostream>

void foo(int a, int b) {
    std::cout << "a=" << a << ", b=" << b << "\n";
}

int main() {
    std::thread t(foo, 1, 2);

    if (t.joinable()) {
        t.join();   // 等待子线程结束(推荐)
    }
    // t.detach(); // 让线程后台运行(需确保资源生命周期安全)
    return 0;
}

要点:

  • std::thread 对象析构时如果仍 joinable() == true,程序会 std::terminate()
  • detach() 不是“更安全”,它只是放弃管理;一旦 detach,线程仍可能访问已销毁对象,风险更高。

3.2 线程对象是“可移动、不可拷贝”的

#include <thread>
#include <utility>
#include <iostream>

void foo1() {}
void foo2() {}

int main() {
    std::thread t1(foo1);
    std::thread t2(std::move(t1));      // t1 所有权转移给 t2

    t1 = std::thread(foo2);             // t1 重新绑定新线程

    std::thread t3;
    t3 = std::move(t2);                 // t2 -> t3

    if (t1.joinable()) t1.join();
    if (t3.joinable()) t3.join();
    return 0;
}

4. 智能指针与线程参数传递(生命周期安全)

4.1 unique_ptr:独占所有权,必须 std::move

#include <thread>
#include <memory>

void foo_unique(std::unique_ptr<int> p) {
    // p 独占该资源
}

int main() {
    auto p = std::make_unique<int>(520);
    std::thread t(foo_unique, std::move(p)); // 必须 move
    t.join();
    return 0;
}

4.2 shared_ptr:共享所有权(引用计数线程安全,但对象本身不一定线程安全)

#include <thread>
#include <memory>

void foo_shared(std::shared_ptr<int> p) {
    // 多线程共享引用计数 OK,但 *p 的读写仍需同步
}

int main() {
    auto p = std::make_shared<int>(520);
    std::thread t(foo_shared, p); // 可拷贝
    t.join();
    return 0;
}

补充:若担心循环引用,使用 std::weak_ptr 断环。


5. RAII 管理线程:scoped_thread(自动 join)

思路:把“必须 join”的规则做成类型约束。

#include <thread>
#include <stdexcept>
#include <utility>

class scoped_thread {
    std::thread t;
public:
    explicit scoped_thread(std::thread t_) : t(std::move(t_)) {
        if (!t.joinable()) throw std::logic_error("thread is not joinable");
    }

    ~scoped_thread() {
        t.join();
    }

    scoped_thread(scoped_thread const&) = delete;
    scoped_thread& operator=(scoped_thread const&) = delete;
};

void foo(int cnt) {}

int main() {
    scoped_thread st(std::thread(foo, 100));
    return 0;
}

更现代的做法:C++20 std::jthread(析构自动 join,并支持 stop_token)。


6. CPU 核心数与并发启动

#include <thread>
#include <vector>
#include <iostream>

void work(int id) {
    std::cout << "id=" << id << ", tid=" << std::this_thread::get_id() << "\n";
}

int main() {
    unsigned n = std::thread::hardware_concurrency(); // 建议值,不保证准确
    std::cout << "hardware_concurrency=" << n << "\n";

    std::vector<std::thread> ts;
    for (int i = 0; i < 10; ++i) ts.emplace_back(work, i);
    for (auto& t : ts) t.join();
    return 0;
}

7. 互斥与锁:从“手动 lock/unlock”升级到 RAII

7.1 std::mutex + std::lock_guard(轻量、推荐默认用)

#include <mutex>
#include <queue>

class MyClass {
    std::queue<int> q;
    std::mutex m;
public:
    void push(int x) {
        std::lock_guard<std::mutex> lk(m);
        q.push(x);
    }

    bool pop(int& out) {
        std::lock_guard<std::mutex> lk(m);
        if (q.empty()) return false;
        out = q.front();
        q.pop();
        return true;
    }
};

7.2 std::unique_lock(更灵活:延迟加锁/尝试加锁/手动 unlock)

常用模式:

  • std::defer_lock:延迟加锁(先构造 lock 对象,后续再 lock)
  • std::try_to_lock:不阻塞尝试加锁
  • std::adopt_lock:把“已持有的锁”交给 lock 对象托管

示例:尝试加锁

#include <mutex>
#include <queue>
#include <iostream>

class MyClass {
    std::queue<int> q;
    std::mutex m;
public:
    void try_push(int x) {
        std::unique_lock<std::mutex> lk(m, std::try_to_lock);
        if (lk.owns_lock()) q.push(x);
        else std::cout << "try_push failed\n";
    }
};

7.3 多把锁避免死锁:std::scoped_lockstd::lock

#include <mutex>

std::mutex m1, m2;

void safe() {
    std::scoped_lock lk(m1, m2); // 一次性锁住,避免死锁
    // ...
}

8. 计时锁与递归锁

8.1 std::timed_mutex

#include <mutex>
#include <chrono>

std::timed_mutex tm;

void foo() {
    if (tm.try_lock_for(std::chrono::milliseconds(100))) {
        // critical section
        tm.unlock();
    }
}

8.2 std::recursive_mutex(允许同一线程重复加锁;通常优先考虑重构而不是依赖它)

#include <mutex>

std::recursive_mutex rm;
int count = 0;

void dfs(int k) {
    if (k == 0) return;
    std::lock_guard<std::recursive_mutex> lk(rm);
    ++count;
    dfs(k - 1);
}

9. 读写锁:std::shared_mutex(C++17)

  • 读:std::shared_lock<std::shared_mutex>(共享锁,可并发读)
  • 写:std::unique_lock<std::shared_mutex>(独占锁,写时阻塞读/写)
#include <shared_mutex>
#include <thread>
#include <vector>
#include <iostream>

class RW {
    std::shared_mutex sm;
    int val = 0;
public:
    void read(int id) {
        std::shared_lock<std::shared_mutex> lk(sm);
        std::cout << "R" << id << " val=" << val << "\n";
    }
    void write(int id) {
        std::unique_lock<std::shared_mutex> lk(sm);
        ++val;
        std::cout << "W" << id << " val=" << val << "\n";
    }
};

10. std::call_once:只初始化一次(线程安全单次执行)

#include <mutex>

std::once_flag flag;
int val = 0;

void init_once() {
    std::call_once(flag, []{
        val = 1; // 初始化逻辑
    });
}

11. 条件变量:生产者-消费者(正确使用 wait + predicate)

关键点:

  • wait 必须配合 std::unique_lock
  • wait(lk, pred) 处理“虚假唤醒”
  • 通常先修改共享状态,再 unlock,最后 notify_*
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
#include <vector>

std::mutex m;
std::condition_variable cv;
std::queue<int> buf;
const size_t BUF_MAX = 10;

int finished_producers = 0;
const int PRODUCERS = 2;

void producer(int id, int n) {
    for (int i = 0; i < n; ++i) {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{ return buf.size() < BUF_MAX; });

        buf.push(i);
        lk.unlock();
        cv.notify_one();
    }

    {
        std::lock_guard<std::mutex> lk(m);
        ++finished_producers;
    }
    cv.notify_all();
}

void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{
            return !buf.empty() || finished_producers == PRODUCERS;
        });

        if (buf.empty() && finished_producers == PRODUCERS) break;

        int x = buf.front();
        buf.pop();
        lk.unlock();
        cv.notify_one();

        // consume x ...
    }
}

int main() {
    std::vector<std::thread> ps, cs;
    for (int i = 0; i < PRODUCERS; ++i) ps.emplace_back(producer, i, 1000);
    for (int i = 0; i < 5; ++i) cs.emplace_back(consumer, i);

    for (auto& t : ps) t.join();
    for (auto& t : cs) t.join();
}

12. 异步任务:std::async / future / promise

12.1 std::async 启动策略

  • std::launch::async:立即开线程/并行执行
  • std::launch::deferred:延迟执行,直到调用 get()/wait(),且通常在调用线程执行
#include <future>

int foo(int a, int b) { return a + b; }

int main() {
    std::future<int> fut = std::async(std::launch::async, foo, 1, 2);
    int r = fut.get(); // 获取结果(阻塞等待)
}

12.2 std::promise 手工传递结果

适合需要在线程函数中“主动设置结果”的场景(或传递异常)。


13. 原子操作:std::atomic 与内存序

13.1 基本 API

  • load / store / exchange
  • 计数:fetch_add / fetch_sub
  • CAS:compare_exchange_weak/strong

13.2 std::atomic_flag 自旋锁(示例)

#include <atomic>

class spinlock_mutex {
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {}
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

13.3 内存序(你笔记里的关键点可以这样归纳)

  • memory_order_relaxed:只保证原子性,不保证跨变量的可见性顺序
  • memory_order_seq_cst:最强,全局一致的顺序(最易推理,可能更慢)
  • 常用工程组合:release(写端)+ acquire(读端)

14. 并行求和的几种写法(思路总结)

你列的 mutex / async / promise / atomic 四套方法方向是对的;补一条工程建议:

  • 示例不要用 1e9 的 vector(内存不现实);讲清模式即可。
  • 真正高性能求和通常用 分块 + 局部累加 + 汇总,避免高竞争共享变量。

15. 线程池(ThreadPool)整理版(更现代的模板写法)

要点:

  • 任务队列 + 条件变量
  • submit() 把任务包装为 packaged_task,返回 future
  • 析构:置 stop、notify_all、join 全部工作线程
#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <type_traits>
#include <utility>

class ThreadPool {
    std::mutex m;
    std::condition_variable cv;
    bool stop = false;
    std::queue<std::function<void()>> tasks;
    std::vector<std::thread> workers;

    void worker() {
        while (true) {
            std::function<void()> task;
            {
                std::unique_lock<std::mutex> lk(m);
                cv.wait(lk, [&]{ return stop || !tasks.empty(); });
                if (stop && tasks.empty()) return;
                task = std::move(tasks.front());
                tasks.pop();
            }
            task();
        }
    }

public:
    explicit ThreadPool(size_t n) {
        workers.reserve(n);
        for (size_t i = 0; i < n; ++i) workers.emplace_back(&ThreadPool::worker, this);
    }

    ~ThreadPool() {
        {
            std::lock_guard<std::mutex> lk(m);
            stop = true;
        }
        cv.notify_all();
        for (auto& t : workers) if (t.joinable()) t.join();
    }

    template <class F, class... Args>
    auto submit(F&& f, Args&&... args)
        -> std::future<std::invoke_result_t<F, Args...>>
    {
        using R = std::invoke_result_t<F, Args...>;

        auto task_ptr = std::make_shared<std::packaged_task<R()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );

        std::future<R> fut = task_ptr->get_future();
        {
            std::lock_guard<std::mutex> lk(m);
            if (stop) throw std::runtime_error("ThreadPool stopped");
            tasks.emplace([task_ptr]{ (*task_ptr)(); });
        }
        cv.notify_one();
        return fut;
    }
};

16. 高频易错点清单

  1. std::thread 析构前必须 join/detach,否则 terminate
  2. shared_ptr 只保证引用计数线程安全,不保证对象读写安全。
  3. 尽量不用手写 mtx.lock()/unlock(),优先 lock_guard/unique_lock
  4. condition_variable 必须用 predicate 防虚假唤醒。
  5. 多把锁用 scoped_lockstd::lock 规避死锁。
  6. 原子内存序不要滥用 relaxed;默认不确定就用 seq_cstacquire/release 成对设计。
  7. detach 线程必须保证:线程访问的一切对象在其结束前都有效(否则悬空引用/崩溃)。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐