当前位置: 首页 > news >正文

C++ 学习 多线程 2025年6月17日18:41:30

多线程(标准线程库 <thread>)

创建线程

#include <iostream>
#include <thread>void hello() {std::cout << "Hello from thread!\n";
}int main() {// 创建线程并执行 hello() std::thread t(hello); //线程对象,传入可调用对象(函数、Lambda、函数对象)t.join();              // 等待线程结束 阻塞主线程,直到子线程完成。
//t.detach():分离线程(线程独立运行,主线程不等待)。
// 仅在明确不需要管理线程生命周期时使用 detach(),并确保资源安全。return 0;
}
传递参数(和平常函数调用不同 注意看):
void print_sum(int a, int b) {std::cout << a + b << "\n";
}int main() {std::thread t(print_sum, 10, 20);  // 传递参数t.join();
}
Lambda 表达式 线程:
std::thread t([] {std::cout << "Lambda thread\n";
});
t.join();

线程同步:

互斥锁(Mutex):防止多个线程访问 共享数据:
#include <mutex>std::mutex mtx;
int shared_data = 0;//原始手动加锁 
void increment() {mtx.lock();          // 加锁:如果其他线程已锁,这里会阻塞等待shared_data++;       // 临界区:唯一线程能执行的代码mtx.unlock();        // 解锁:允许其他线程进入
}
//风险:如果 shared_data++ 抛出异常,unlock() 可能不被执行,导致死锁。void safe_increment() {std::lock_guard<std::mutex> lock(mtx);  // 构造时自动加锁shared_data++;                          // 临界区
} // 析构时自动解锁(即使发生异常)
//RAII(资源获取即初始化):利用对象生命周期自动管理锁,避免忘记解锁。加锁后的正确流程
线程A加锁 → shared_data++(变为1)→ 解锁线程B加锁 → shared_data++(变为2)→ 解锁
结果:shared_data = 2。
高阶用法:
void flexible_increment() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁// ...其他非临界区代码...lock.lock();       // 手动加锁shared_data++;lock.unlock();     // 可手动提前解锁
}=================================================//多个锁时,按固定顺序获取:
std::mutex mtx1, mtx2;void safe_operation() {std::lock(mtx1, mtx2); // 同时加锁(避免死锁)std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);// 操作多个共享资源
}=============错误示范===============
int* get_data() {std::lock_guard<std::mutex> lock(mtx);return &shared_data; // ❌ 危险!锁失效后仍可访问
}注意事项
锁粒度:锁的范围应尽量小(减少阻塞时间)。避免嵌套锁:容易导致死锁。不要返回锁保护的指针/引用:会破坏封装性。
线程之间通知机制 (条件变量)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;  // 条件变量依赖的共享状态// 消费者线程(等待数据)
void consumer() {std::unique_lock<std::mutex> lock(mtx);std::cout << "消费者: 等待数据...\n";cv.wait(lock, [] { return data_ready; });  // 等待条件成立std::cout << "消费者: 收到数据,开始处理!\n";
}// 生产者线程(准备数据)
void producer() {std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟耗时操作{std::lock_guard<std::mutex> lock(mtx);data_ready = true;  // 修改共享状态std::cout << "生产者: 数据已准备!\n";}cv.notify_one();  // 通知等待的消费者线程
}int main() {std::thread t1(consumer);  // 消费者线程(等待)std::thread t2(producer);  // 生产者线程(通知)t1.join();t2.join();return 0;
}//输出效果 
消费者: 等待数据...
生产者: 数据已准备!
消费者: 收到数据,开始处理!关键点解析
条件变量 (std::condition_variable)用于线程间的条件同步,允许线程阻塞直到某个条件成立。必须与 std::mutex 和 一个共享状态变量(如 bool data_ready)配合使用。
-------------------------------------------------------------------------
cv.wait(lock, predicate) 的工作原理步骤1:线程获取锁后检查条件(predicate)。步骤2:若条件为 false,线程释放锁并进入阻塞状态,等待通知。步骤3:当其他线程调用 notify_one() 时,线程被唤醒,重新获取锁并再次检查条件。步骤4:若条件为 true,线程继续执行;否则继续等待。为什么需要 data_ready 变量?避免虚假唤醒:操作系统可能意外唤醒线程,因此需要显式检查条件。状态同步:明确线程间的通信意图(如“数据已准备好”)。锁的作用域生产者:修改 data_ready 时必须加锁(lock_guard)。消费者:wait() 会自动释放锁,唤醒后重新获取锁。

异步任务(得重点掌握):

std::async

异步执行函数,返回 std::future

#include <future>int compute() { return 42; }int main() {std::future<int> result = std::async(compute);std::cout << "Result: " << result.get() << "\n";  // 阻塞获取结果
}std::launch::async:立即异步执行。std::launch::deferred:延迟到 get() 时执行。

=====================================================================

std::packaged_task

将函数包装为可异步调用的任务:

std::packaged_task<int()> task([] { return 7 * 6; }); //异步包装
std::future<int> result = task.get_future(); //future 用于稍后获取异步结果。
std::thread t(std::move(task));  // 在线程中执行
t.join();
std::cout << "Result: " << result.get() << "\n";==================================================
通过 std::move:将 task 的所有权转移给线程 t,避免拷贝。转移后,原 task 对象变为 空状态(不能再调用)

线程管理 

获取硬件线程数:

std::thread::hardware_concurrency()

 返回的是 当前计算机硬件支持的线程并发数(通常等于逻辑CPU核心数)

线程不超过线程数时 效果最佳

unsigned cores = std::thread::hardware_concurrency();
std::cout << "CPU cores: " << cores << "\n";
//std::thread::hardware_concurrency() 返回的是 
//当前计算机硬件支持的线程并发数(通常等于逻辑CPU核心数)4 核 4 线程 CPU → 输出 44 核 8 线程 CPU → 输出 8苹果 M1 Max (10 核) → 输出 10
线程局部储存(每个线程独享的变量TLS):
thread_local int counter = 0;  // 每个线程有独立副本

原子操作(无须锁的安全操作)

为什么不需要锁?

1.硬件支持

  • CPU 原子指令:现代 CPU 提供专门的指令(如 x86 的 LOCK XADD、ARM 的 LDREX/STREX)确保单条指令完成“读取-修改-写入”操作,不会被线程切换打断。

  • 缓存一致性协议:通过 MESI 等协议保证多核间对原子变量的可见性。


2. 编译器与语言标准保障

  • 编译器屏障std::atomic 操作会阻止编译器重排序相关指令。

  • 内存顺序控制:支持灵活的内存序(如 memory_order_relaxedmemory_order_seq_cst),平衡性能与一致性需求。

#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> counter(0);void increment(int n) {for (int i = 0; i < n; ++i) {counter++;  // 原子自增}
}int main() {std::thread t1(increment, 100000);std::thread t2(increment, 100000);t1.join(); t2.join();std::cout << "Counter: " << counter << "\n";  // 保证输出 200000return 0;
}
特性std::atomicstd::mutex
实现层级硬件指令 + 编译器优化操作系统级锁(可能涉及系统调用)
粒度单个变量操作保护任意代码块
性能极高(无锁设计)较高(存在锁争用开销)
适用场景简单变量(int、bool、指针等)复杂逻辑或跨多个变量的操作

 

std::atomic 的局限性
  • 仅适用于标量类型:对结构体等复杂类型需自定义或使用锁。

  • 内存序选择:错误的内存序可能导致意外行为(如 memory_order_relaxed 不保证顺序)。

死锁预防:

避免嵌套锁:按固定顺序加锁。

使用 std::lock 同时锁多个互斥量(前面互斥锁有拓展)

std::mutex mtx1, mtx2;
std::lock(mtx1, mtx2);  // 同时加锁(避免死锁)
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);

线程池(运用第三方库实现)广泛应用于需要高并发处理短任务的场景(如HTTP服务器、并行计算等)

第三方库(如 BS::thread_pool):
#include "thread_pool.hpp"          // 引入线程池库头文件
BS::thread_pool pool;              // 创建默认线程池(线程数=硬件并发数)
auto task = pool.submit([] { return 42; });  // 提交Lambda任务
std::cout << task.get() << "\n";   // 阻塞等待并获取结果
BS::thread_pool线程池类,管理一组工作线程(通常数量=CPU核心数)。
pool.submit()提交任务(函数/Lambda)到线程池,返回 std::future 对象。
task.get()阻塞调用线程,直到任务完成并返回结果(类似 std::future::get)。

 

工作流程
  1. 线程池初始化

    • 创建时默认启动 std::thread::hardware_concurrency() 个工作线程。

    • 线程空闲时会自动从任务队列中取任务执行。

  2. 任务提交

    • submit 将 Lambda [] { return 42; } 封装为任务,放入队列。

    • 返回的 task 是一个 std::future<int>,用于后续获取结果。

  3. 结果获取

    • task.get() 会阻塞主线程,直到某个工作线程完成该任务。

    • 最终输出 42

对比原生 std::thread
特性BS::thread_poolstd::thread
线程管理自动复用线程(避免频繁创建/销毁)需手动管理线程生命周期
任务队列支持批量提交任务需自行实现任务队列
开销低(线程复用)高(每次任务新建线程)
适用场景大量短任务少量长任务
 拓展用法示例:
//批量提交任务
std::vector<std::future<int>> results;
for (int i = 0; i < 10; ++i) {results.push_back(pool.submit([i] { return i * i; }));
}
for (auto& r : results) {std::cout << r.get() << " ";  // 输出 0 1 4 9 16 25 36 49 64 81
}//获取线程池信息
std::cout << "线程数: " << pool.get_thread_count() << "\n";//等待所有任务完成
pool.wait();  // 阻塞直到所有任务完成(不销毁线程)

总结

功能工具头文件
线程创建std::thread<thread>
互斥锁std::mutexstd::lock_guard<mutex>
条件变量std::condition_variable<condition_variable>
异步任务std::asyncstd::future<future>
原子操作std::atomic<atomic>
线程局部存储thread_local语言内置

相关文章:

  • 基于深度学习的智能语音情感分析系统:技术与实践
  • [Think] Libuv | Node.js | nix vs docker
  • Redis 核心数据类型及典型使用场景详解
  • HTTP 请求中的 `Content-Type` 类型详解及前后端示例(Vue + Spring Boot)
  • Stripformer: Strip Transformer for Fast Image Deblurring论文阅读
  • c++学习-多态
  • 从零到一:C语言基础入门学习路线与核心知识点全解析
  • Redis的GEO详解
  • 82.多级抽取滤波器,设计抗混叠滤波器时采样频率是基于抽取之前的设计的
  • Lua基础复习之Lua元表
  • C++——基础知识
  • 论文笔记 <交通灯> IntelliLight:一种用于智能交通灯控制的强化学习方法
  • RISC-V向量扩展与GPU协处理:开源加速器设计新范式——对比NVDLA与香山架构的指令集融合方案
  • Greenplum 与 PostgreSQL 的关系
  • 005微信小程序npm包_全局数据共享和分包
  • # 我使用过的 HTML + CSS 实践总结笔记(含说明)
  • 密度泛函涨落理论在医疗人工智能中的应用与展望:多尺度物理驱动智能的新范式
  • 【Vue】Vue2/3全局属性配置全攻略
  • 实验分享|自研局部DIC-GPU算法与开源GPU算法对比实验
  • SpringBoot-Actuator依赖项的作用配置 Heapdump堆栈信息泄露
  • wordpress 登录页加密/沧州网站推广优化
  • 个人做动漫资源网站/app投放推广
  • 厦门市网站建设app开发/百度网站联系方式
  • 做网站后台用什么语言/2022年最新最有效的营销模式
  • 平台推广网站排名/优化网站seo方案
  • 定制型网站开发/福州seo快速排名软件