HoRain云--C++多线程编程
🎬 HoRain 云小助手:个人主页
⛺️生活的理想,就是为了理想的生活!
⛳️ 推荐
前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。
目录
⛳️ 推荐
一、std::thread—— 线程本体
二、互斥与 RAII 锁 —— 对应 Java 的 synchronized
三、condition_variable—— 生产者-消费者标配
四、std::atomic与内存序 —— C++ 独有的深坑
五、future/ promise/ async—— 异步任务
六、C++20 新增值得提一嘴
七、跟 Java 多线程的对照(面试能串)
八、工程现实的两个"不像 Java"的点
九、几句面试加分
C++ 多线程跟 Java 那条线比,最大感受是"晚熟但贴硬件"——Java 出生就带Thread/synchronized,C++ 熬到C++11 才把<thread>/<mutex>/<atomic>/<future>收进标准,之前全靠 pthread / WinAPI / Boost。所以现在聊"C++ 多线程"默认指C++11/14/17/20 标准库,下面按"线程 → 锁 → 条件变量 → 原子 → 异步 → C++20 新增 → 跟 Java 对照的坑"走。
一、std::thread—— 线程本体
#include <thread> #include <iostream> using namespace std; void task(int x) { cout << x << endl; } int main() { thread t(task, 42); // 传可调用对象 + 参数(参数会拷贝进线程) if (t.joinable()) { t.join(); // 等它跑完 // t.detach(); // 或分离,后台跑,主线程不管了 } }几个跟 Java 立刻不同的点:
std::thread不可拷贝,只能移动——线程是独占资源析构时如果还是
joinable()(既没join也没detach)→直接std::terminate,比 Java 狠参数默认值拷贝,想传引用得
std::ref(obj),传智能指针shared_ptr值传会 +1 引用计数(线程安全),unique_ptr得std::movethis_thread::get_id()拿 ID,hardware_concurrency()拿 CPU 核数
💡 C++20 的
std::jthread 才是"现代首选"——析构自动join(),还带std::stop_token协作式中断,不用自己写取消标志位了。
二、互斥与 RAII 锁 —— 对应 Java 的synchronized
mutex m; int cnt = 0; void add() { lock_guard<mutex> lk(m); // 构造加锁,析构解锁,noexcept ++cnt; } // 出作用域自动 unlock,异常也安全C++ 没有finally,锁的释放全靠 RAII 析构——这是跟 Java 的根本差异(Java 靠synchronized块或ReentrantLock+try/finally)。
锁家族:
类型 | 用途 |
|---|---|
| 基础互斥,不可拷贝 |
| 同一线程可重复加锁(慎用,设计味道) |
|
|
| 读写锁,读多写少场景 |
RAII 壳子俩兄弟:
lock_guard:轻量,构造锁、析构解锁,不能手动 unlock——够用就它unique_lock:功能全——延迟加锁(defer_lock)、手动 lock/unlock、可移动、条件变量必须用它(因为wait()要临时释放锁)
死锁防护——C++17 的std::scoped_lock:一次锁多个,内部按地址排序加锁,无论你传的顺序咋样都不会死锁:
scoped_lock lk(m1, m2, m3); // 同时锁三个,不会死锁对标 Java 的"锁排序"套路,但 C++ 标准库直接给你包好了。
三、condition_variable—— 生产者-消费者标配
跟 Java 的wait()/notify()长得很像,但两个坑 Java 程序员过来容易栽:
mutex m; condition_variable cv; queue<int> q; bool done = false; // 消费者 void consumer() { unique_lock<mutex> lk(m); cv.wait(lk, []{ return !q.empty() || done; }); // while 谓词版,防虚假唤醒 // ... } // 生产者 void producer() { { lock_guard<mutex> lk(m); q.push(1); } // 通知前解锁,减少被唤醒线程的抢锁争用 cv.notify_one(); }关键点:
必须配
unique_lock(不是lock_guard)——因为wait()要原子地释放锁 + 挂起,唤醒后重新加锁wait(lk, predicate)用谓词重载,等价于while(!pred) wait(lk)——防虚假唤醒(spurious wakeup),这是 POSIX / Java / C++ 三边共有的,Java 也要求while不if,但 C++ 这重载帮你包了一层notify_one()/notify_all()—— 通知不需要持锁(标准允许多种做法,但推荐通知前解锁减少争用)还有个
condition_variable_any,能配任意BasicLockable(含shared_lock),但慢一点
四、std::atomic与内存序 —— C++ 独有的深坑
Java 那边volatile只保可见性不保原子性,得VarHandle或AtomicInteger;C++ 的std::atomic一步到位,还多了六级内存序——这是 Java 程序员过来最容易懵的地方。
atomic<bool> ready{false}; int data = 0; void producer() { data = 42; ready.store(true, memory_order_release); // 前面的写不会被重排到 store 之后 } void consumer() { while (!ready.load(memory_order_acquire)) // 后面的读不会被重排到 load 之前 ; assert(data == 42); // 永不为真?不,acquire-release 保证了能看到 42 }六级memory_order:
序 | 含义 | 场景 |
|---|---|---|
| 只保原子性,不管可见性顺序 | 计数器 |
| 数据依赖链同步(C++26 弃用,实际被 acquire 替) | — |
| load 用,之后的读写不许重排到 load 前 | 读标志后读数据 |
| store 用,之前的读写不许重排到 store 后 | 写完数据设标志 |
| read-modify-write 用,双向屏障 | CAS、 |
| 全序,默认档,所有原子操作全局一致顺序 | 调试、保守写法 |
Release-Acquire 配对是 C++ 无锁编程的命门:release 之前的所有写,对配对 acquire 成功读到该值的线程全部可见——上面那个producer/consumer例子就是这个语义,等价于 Java 的"volatile 写 + volatile 读"但 C++ 更精细可控。
⚠️ 默认
seq_cst最安全但最慢(全内存屏障);无锁代码调优才往下换 acquire/release;relaxed只在"我只关心这个变量原子性、不关心别的可见性"时才用(比如纯计数器)。
五、future/promise/async—— 异步任务
对应 Java 的Future/CompletableFuture,但 C++ 这套更"裸":
auto fut = async([] { this_thread::sleep_for(1s); return 42; }); cout << fut.get() << endl; // 阻塞等结果,只能 get 一次std::async启动策略:launch::async(必开新线程)/launch::deferred(第一次 get 时同步执行)/ 默认由实现决定(可能线程池可能延迟,坑点)std::promise+std::future手动搭管道:一个线程set_value(),另一个future.get()等shared_future:多个线程等同一个结果(普通future只能 get 一次)
💡 想要 Java
CompletableFuture那种thenApply/thenCompose链式?C++ 标准库没有,得自己拼或用std::experimental::future(很少用),或者上第三方(folly::Future、Qt 等)。
六、C++20 新增值得提一嘴
std::jthread:自动 join +stop_token协作取消(上面提过)std::counting_semaphore/binary_semaphore:终于有信号量了(Java 老早就有)std::latch/std::barrier:多线程同步原语(等 N 个线程到齐)协程(
co_await/co_yield/co_return):C++20 最大的新玩具,但标准只给了底层设施,高阶封装得等库(cppcoro、ASIO 那路)
七、跟 Java 多线程的对照(面试能串)
维度 | Java | C++ |
|---|---|---|
线程本体 |
|
|
互斥 |
|
|
读写锁 |
|
|
等待/通知 |
|
|
原子类 |
|
|
异步 |
|
|
内存模型 | happens-before,volatile 弱 | 六级 |
协程 | Project Loom 虚拟线程(JDK 21) | C++20 协程(底层,无 runtime) |
死锁防护 | 锁排序 / |
|
八、工程现实的两个"不像 Java"的点
1. 不是所有 C++ 项目都用std::thread
游戏引擎、高频交易、嵌入式——要么禁用异常 + 禁用 RTTI + 自己线程池,要么直接 pthread / 平台 API。std::thread在那些场景被认为"抽象过重 + 不可控"(join 模型、异常传播、TLS 行为)。
2. 线程参数传递是高频坑
void f(int& x) { x++; } int val = 0; thread t(f, val); // ❌ 编译不过,参数被拷贝,引用传不进去 thread t(f, ref(val)); // ✅ 必须 std::ref局部变量指针传给detach()的线程 = 野指针经典案,Java 没有这问题(GC 兜着)。
九、几句面试加分
std::thread析构时joinable() == true→terminate,所以要么 join 要么 detach,不能放着条件变量
wait必须while(pred)或谓词重载,虚假唤醒是规范不是 bugscoped_lock比手搓"先锁 A 再锁 B"稳,C++17 起首选vector的线程安全等级是Basic——多线各读或单线写,混着来 UB;JavaVector是 synchronized 的(但慢,现在用CopyOnWriteArrayList或ConcurrentHashMap)C++ 的
volatile跟 Java 的volatile完全是两回事——C++volatile只防编译器优化重排,不保原子性也不保跨线程可见性,多线程同步别用它,用atomic
如果想再往下挖,可以聊C++20 协程的co_await到底怎么串异步 IO、无锁队列怎么用 acquire-release 搭,或者memory_order_seq_cst为什么比 acquire-release 慢一个量级——挑一个?
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙
