
1为什么需要C的std::thread在 C11 之前C 没有标准的线程库编写跨平台并发程序非常痛苦Linux 下依赖pthread库C 语言风格面向过程Windows 下依赖CreateThread等 Win32 API不同平台接口不兼容代码无法直接移植没有利用 C 的面向对象特性和现代语法右值引用、可变模板参数std::thread 的核心优势跨平台底层封装了不同系统的线程 API一套代码在 Linux/Windows/macOS 都能运行面向对象以类的形式管理线程生命周期更符合 C 的编程范式语法更友好支持任意可调用对象函数、lambda、函数对象、类成员函数作为线程入口参数传递更灵活支持移动语义线程对象不可拷贝但可以移动方便在容器中存储和传递2std::thread的基本用法1线程的创建与启动std::thread有4个构造函数日常最常用的是带可调用对象和参数的构造函数template class Fn, class... Args explicit thread(Fn fn, Args... args);该构造函数接收一个可调用的对象fn和任意数量的参数args创建后立即执行fn(args...)#include iostream #include thread #include string // 1. 普通函数作为线程入口 void print_number(int n) { for (int i 0; i n; i) { std::cout 普通函数线程 i std::endl; } } // 2. 类成员函数作为线程入口 class Task { public: void print_string(const std::string s) { for (int i 0; i 3; i) { std::cout 成员函数线程 s std::endl; } } }; int main() { // 方式1普通函数 std::thread t1(print_number, 5); // 方式2lambda表达式 std::thread t2([]() { for (int i 0; i 3; i) { std::cout Lambda线程 i std::endl; } }); // 方式3类成员函数需要传递对象指针/引用作为第一个参数 Task task; std::thread t3(Task::print_string, task, hello); // 等待所有线程执行完成 t1.join(); t2.join(); t3.join(); return 0; }2线程等待joinjoin()函数会阻塞当前线程通常是主线程直到被调用的线程执行完成。必须调用 join ()否则如果子线程还在运行主线程先结束整个进程会被操作系统终止子线程会被强行杀死未调用 join () 的线程对象在析构时会调用std::terminate()导致程序崩溃3线程的分离detachdetach()函数会将线程对象与底层执行线程分离分离后的线程成为守护线程在后台独立运行主线程不再需要等待它。重要注意事项分离后的线程无法再被 join ()其生命周期由操作系统管理绝对不要让分离线程持有局部变量的引用 / 指针因为主线程退出后局部变量会被销毁导致野指针分离线程适用于不需要返回结果、生命周期与进程一致的后台任务如日志写入、心跳检测错误示例void bad_detach_example() { int local_var 10; std::thread t([local_var]() { // 捕获局部变量的引用 std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout local_var std::endl; // 未定义行为local_var已被销毁 }); t.detach(); } // 函数结束local_var被销毁3线程参数传递的细节1为什么默认是值传递std::thread 的构造函数会拷贝所有参数到线程内部的元组中然后再传递给线程函数。这是因为线程可能在参数所在的作用域结束后才执行值传递可以避免野指针底层系统 API如 pthread_create只接受一个void*参数C 需要先打包参数再传递2如何传递引用如果需要传递引用必须使用std::ref或std::cref包装参数否则会编译报错或产生拷贝void modify_value(int x) { x 100; } int main() { int a 10; // std::thread t(modify_value, a); // 编译错误无法将int转换为int std::thread t(modify_value, std::ref(a)); // 正确传递引用 t.join(); std::cout a std::endl; // 输出100 return 0; }3传递右值和移动语义对于不可拷贝的对象如 std::unique_ptr可以使用std::move将其转移到线程中#include memory void process_data(std::unique_ptrint ptr) { std::cout *ptr std::endl; } int main() { auto ptr std::make_uniqueint(42); // std::thread t(process_data, ptr); // 编译错误unique_ptr不可拷贝 std::thread t(process_data, std::move(ptr)); // 正确转移所有权 t.join(); // 此时ptr为空不能再使用 return 0; }4线程对象的移动语义std::thread不支持拷贝构造和拷贝赋值但支持移动构造和移动赋值。这是因为一个线程对象只能关联一个底层执行线程。#include vector void thread_func(int id) { std::cout 线程 id 运行中 std::endl; } int main() { // 移动构造 std::thread t1(thread_func, 1); std::thread t2 std::move(t1); // t1失去线程所有权变为空线程 // t1.join(); // 错误t1不关联任何线程 // 移动赋值 std::thread t3; t3 std::move(t2); // t2失去所有权 t3.join(); // 在容器中存储线程必须用移动语义 std::vectorstd::thread threads; for (int i 0; i 3; i) { threads.emplace_back(thread_func, i); // 直接构造线程对象 // 也可以threads.push_back(std::thread(thread_func, i)); } // 等待所有线程完成 for (auto t : threads) { if (t.joinable()) { // 必须检查是否可join t.join(); } } return 0; }重要在移动或析构线程对象前必须确保已经调用了join()或detach()否则会导致程序崩溃。5this_thread命名空间std::this_thread命名空间提供了操作当前执行线程的全局函数这里介绍几个函数作用细节补充get_id()获取当前线程的 ID返回std::thread::id类型支持比较和流输出yield()主动让出 CPU 时间片提示操作系统调度其他线程不保证立即让出sleep_for(duration)阻塞当前线程指定时长阻塞时间至少为指定时长可能因调度延迟更长sleep_until(time_point)阻塞当前线程直到指定时间点基于系统时钟同样可能有调度延迟用法#include chrono int main() { // 获取当前线程ID std::cout 主线程ID std::this_thread::get_id() std::endl; // 睡眠1秒 std::cout 开始睡眠... std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout 睡眠结束 std::endl; // 睡眠到下一个整秒 auto now std::chrono::system_clock::now(); auto next_second now std::chrono::seconds(1); std::this_thread::sleep_until(next_second); return 0; }6线程的补充1线程的异常处理如果线程函数抛出异常且未被捕获会导致整个进程崩溃。必须在线程函数内部捕获所有异常void safe_thread_func() { try { // 可能抛出异常的代码 throw std::runtime_error(线程内部错误); } catch (const std::exception e) { std::cerr 线程异常 e.what() std::endl; } }2thread_local存储这是C11引入的存储类说明符用于声明线程局部变量。每个线程都有自己独立的变量副本互不干扰thread_local int counter 0; // 每个线程都有自己的counter void increment() { for (int i 0; i 3; i) { counter; std::cout 线程 std::this_thread::get_id() counter: counter std::endl; } } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); return 0; }