多线程(标准线程库 <thread>
)
创建线程
cpp
#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;
}
传递参数(和平常函数调用不同 注意看):
cpp
void print_sum(int a, int b) {
std::cout << a + b << "\n";
}
int main() {
std::thread t(print_sum, 10, 20); // 传递参数
t.join();
}
Lambda 表达式 线程:
cpp
std::thread t([] {
std::cout << "Lambda thread\n";
});
t.join();
线程同步:
互斥锁(Mutex):防止多个线程访问 共享数据:
cpp
#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。
高阶用法:
cpp
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; // ❌ 危险!锁失效后仍可访问
}
注意事项
锁粒度:锁的范围应尽量小(减少阻塞时间)。
避免嵌套锁:容易导致死锁。
不要返回锁保护的指针/引用:会破坏封装性。
线程之间通知机制 (条件变量)
cpp
#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
:
cpp#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
将函数包装为可异步调用的任务:
cppstd::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核心数)
线程不超过线程数时 效果最佳
cpp
unsigned cores = std::thread::hardware_concurrency();
std::cout << "CPU cores: " << cores << "\n";
//std::thread::hardware_concurrency() 返回的是
//当前计算机硬件支持的线程并发数(通常等于逻辑CPU核心数)
4 核 4 线程 CPU → 输出 4
4 核 8 线程 CPU → 输出 8
苹果 M1 Max (10 核) → 输出 10
线程局部储存(每个线程独享的变量TLS):
cpp
thread_local int counter = 0; // 每个线程有独立副本
原子操作(无须锁的安全操作)
为什么不需要锁?
1.硬件支持
CPU 原子指令 :现代 CPU 提供专门的指令(如 x86 的
LOCK XADD
、ARM 的LDREX/STREX
)确保单条指令完成"读取-修改-写入"操作,不会被线程切换打断。缓存一致性协议:通过 MESI 等协议保证多核间对原子变量的可见性。
2. 编译器与语言标准保障
编译器屏障 :
std::atomic
操作会阻止编译器重排序相关指令。内存顺序控制 :支持灵活的内存序(如
memory_order_relaxed
、memory_order_seq_cst
),平衡性能与一致性需求。
cpp
#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"; // 保证输出 200000
return 0;
}
特性 | std::atomic |
std::mutex |
---|---|---|
实现层级 | 硬件指令 + 编译器优化 | 操作系统级锁(可能涉及系统调用) |
粒度 | 单个变量操作 | 保护任意代码块 |
性能 | 极高(无锁设计) | 较高(存在锁争用开销) |
适用场景 | 简单变量(int、bool、指针等) | 复杂逻辑或跨多个变量的操作 |
std::atomic
的局限性
-
仅适用于标量类型:对结构体等复杂类型需自定义或使用锁。
-
内存序选择 :错误的内存序可能导致意外行为(如
memory_order_relaxed
不保证顺序)。
死锁预防:
避免嵌套锁:按固定顺序加锁。
使用
std::lock
同时锁多个互斥量(前面互斥锁有拓展):
cppstd::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):
cpp
#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
)。 |
工作流程
线程池初始化
创建时默认启动
std::thread::hardware_concurrency()
个工作线程。线程空闲时会自动从任务队列中取任务执行。
任务提交
submit
将 Lambda[] { return 42; }
封装为任务,放入队列。返回的
task
是一个std::future<int>
,用于后续获取结果。结果获取
task.get()
会阻塞主线程,直到某个工作线程完成该任务。最终输出
42
。
对比原生 std::thread
特性 | BS::thread_pool |
std::thread |
---|---|---|
线程管理 | 自动复用线程(避免频繁创建/销毁) | 需手动管理线程生命周期 |
任务队列 | 支持批量提交任务 | 需自行实现任务队列 |
开销 | 低(线程复用) | 高(每次任务新建线程) |
适用场景 | 大量短任务 | 少量长任务 |
拓展用法示例:
cpp
//批量提交任务
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::mutex , std::lock_guard |
<mutex> |
条件变量 | std::condition_variable |
<condition_variable> |
异步任务 | std::async , std::future |
<future> |
原子操作 | std::atomic |
<atomic> |
线程局部存储 | thread_local |
语言内置 |