RAII 与智能指
·
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++20std::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_lock 或 std::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. 高频易错点清单
std::thread析构前必须join/detach,否则terminate。shared_ptr只保证引用计数线程安全,不保证对象读写安全。- 尽量不用手写
mtx.lock()/unlock(),优先lock_guard/unique_lock。 condition_variable必须用 predicate 防虚假唤醒。- 多把锁用
scoped_lock或std::lock规避死锁。 - 原子内存序不要滥用
relaxed;默认不确定就用seq_cst或acquire/release成对设计。 detach线程必须保证:线程访问的一切对象在其结束前都有效(否则悬空引用/崩溃)。
更多推荐



所有评论(0)