HoRain云--C++多线程编程

HoRain云--C++多线程编程
HoRain 云小助手个人主页⛺️生活的理想就是为了理想的生活!⛳️ 推荐前些天发现了一个超棒的服务器购买网站性价比超高大内存超划算忍不住分享一下给大家。点击跳转到网站。目录⛳️ 推荐一、std::thread—— 线程本体二、互斥与 RAII 锁 —— 对应 Java 的 synchronized三、condition_variable—— 生产者-消费者标配四、std::atomic与内存序 —— C 独有的深坑五、future/ promise/ async—— 异步任务六、C20 新增值得提一嘴七、跟 Java 多线程的对照面试能串八、工程现实的两个不像 Java的点九、几句面试加分C 多线程跟 Java 那条线比最大感受是晚熟但贴硬件——Java 出生就带Thread/synchronizedC 熬到C11 才把thread/mutex/atomic/future收进标准之前全靠 pthread / WinAPI / Boost。所以现在聊C 多线程默认指C11/14/17/20 标准库下面按线程 → 锁 → 条件变量 → 原子 → 异步 → C20 新增 → 跟 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()拿 IDhardware_concurrency()拿 CPU 核数 C20 的std::jthread​ 才是现代首选——析构自动join()还带std::stop_token协作式中断不用自己写取消标志位了。二、互斥与 RAII 锁 —— 对应 Java 的synchronizedmutex m; int cnt 0; void add() { lock_guardmutex lk(m); // 构造加锁析构解锁noexcept cnt; } // 出作用域自动 unlock异常也安全C 没有finally锁的释放全靠 RAII 析构——这是跟 Java 的根本差异Java 靠synchronized块或ReentrantLocktry/finally。锁家族类型用途std::mutex基础互斥不可拷贝std::recursive_mutex同一线程可重复加锁慎用设计味道std::timed_mutextry_lock_for()超时版std::shared_mutex(C17)读写锁读多写少场景RAII 壳子俩兄弟lock_guard轻量构造锁、析构解锁不能手动 unlock——够用就它unique_lock功能全——延迟加锁defer_lock、手动 lock/unlock、可移动、条件变量必须用它因为wait()要临时释放锁死锁防护——C17 的std::scoped_lock一次锁多个内部按地址排序加锁无论你传的顺序咋样都不会死锁scoped_lock lk(m1, m2, m3); // 同时锁三个不会死锁对标 Java 的锁排序套路但 C 标准库直接给你包好了。三、condition_variable—— 生产者-消费者标配跟 Java 的wait()/notify()长得很像但两个坑 Java 程序员过来容易栽mutex m; condition_variable cv; queueint q; bool done false; // 消费者 void consumer() { unique_lockmutex lk(m); cv.wait(lk, []{ return !q.empty() || done; }); // while 谓词版防虚假唤醒 // ... } // 生产者 void producer() { { lock_guardmutex 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或AtomicIntegerC 的std::atomic一步到位还多了六级内存序——这是 Java 程序员过来最容易懵的地方。atomicbool 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序含义场景relaxed只保原子性不管可见性顺序计数器consume数据依赖链同步C26 弃用实际被 acquire 替—acquireload 用之后的读写不许重排到 load 前​读标志后读数据releasestore 用之前的读写不许重排到 store 后​写完数据设标志acq_relread-modify-write 用双向屏障CAS、fetch_addseq_cst全序默认档所有原子操作全局一致顺序​调试、保守写法Release-Acquire 配对是 C 无锁编程的命门release 之前的所有写对配对 acquire 成功读到该值的线程全部可见——上面那个producer/consumer例子就是这个语义等价于 Java 的volatile 写 volatile 读但 C 更精细可控。⚠️ 默认seq_cst最安全但最慢全内存屏障无锁代码调优才往下换 acquire/releaserelaxed只在我只关心这个变量原子性、不关心别的可见性时才用比如纯计数器。五、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::promisestd::future手动搭管道一个线程set_value()另一个future.get()等shared_future多个线程等同一个结果普通future只能 get 一次 想要 JavaCompletableFuture那种thenApply/thenCompose链式C 标准库没有得自己拼或用std::experimental::future很少用或者上第三方folly::Future、Qt 等。六、C20 新增值得提一嘴std::jthread自动 join stop_token协作取消上面提过std::counting_semaphore/binary_semaphore终于有信号量了Java 老早就有std::latch/std::barrier多线程同步原语等 N 个线程到齐协程co_await/co_yield/co_returnC20 最大的新玩具但标准只给了底层设施高阶封装得等库cppcoro、ASIO 那路七、跟 Java 多线程的对照面试能串维度JavaC线程本体Thread/ 线程池std::thread/jthread互斥synchronized/ReentrantLockstd::mutex RAIIlock_guard/unique_lock读写锁ReentrantReadWriteLockstd::shared_mutex(C17)等待/通知wait()/notify()(Object)condition_variableunique_lock原子类AtomicInteger等std::atomicT 六级内存序异步CompletableFuturestd::future/async无链式内存模型happens-beforevolatile 弱六级memory_orderseq_cst默认协程Project Loom 虚拟线程JDK 21C20 协程底层无 runtime死锁防护锁排序 /tryLock(timeout)scoped_lockC17自动排序八、工程现实的两个不像 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稳C17 起首选vector的线程安全等级是Basic——多线各读或单线写混着来 UBJavaVector是 synchronized 的但慢现在用CopyOnWriteArrayList或ConcurrentHashMapC 的volatile跟 Java 的volatile完全是两回事——Cvolatile只防编译器优化重排不保原子性也不保跨线程可见性多线程同步别用它用atomic如果想再往下挖可以聊C20 协程的co_await到底怎么串异步 IO、无锁队列怎么用 acquire-release 搭或者memory_order_seq_cst为什么比 acquire-release 慢一个量级——挑一个❤️❤️❤️本人水平有限如有纰漏欢迎各位大佬评论批评指正如果觉得这篇文对你有帮助的话也请给个点赞、收藏下吧非常感谢! Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧