【C++ 多线程实战精讲】std::thread 线程创建 / 传参 / 同步 / 智能指针 / 生命周期管理
前言
C++11 正式推出了标准多线程库<thread>,让跨平台多线程开发变得简单高效。但多线程的坑非常多:线程传参、对象生命周期、数据竞争、锁使用、指针悬空、析构崩溃……
本文基于完整可运行工程代码,带你彻底掌握:
线程创建、join /detach、生命周期管理
线程传参全场景:值、引用、指针、智能指针、移动语义
线程同步:mutex /lock_guard 解决数据竞争
自定义类 Int 配合多线程,观察构造 / 析构 / 拷贝 / 移动
一、前置:自定义 Int 类(完整四大件 + 运算符重载)
为了测试多线程下对象拷贝、移动、生命周期、传参行为,我们先实现一个带完整日志的Int类。包含:构造、析构、拷贝构造 / 赋值、移动构造 / 赋值、运算符重载。
// Int.hpp #pragma once #include <iostream> using namespace std; class Int { private: int value; public: // 构造 Int(int x, int y) :value(x + y) { cout << "Create Int(int,int): " << this << " " << value << endl; } Int(int x = 0) :value(x) { cout << "Create Int(int=0): " << this << " " << value << endl; } // 析构 ~Int() { cout << "Destroy Int: " << this << " " << value << endl; value = -1; } // 拷贝 Int(const Int& it) :value(it.value) { cout << &it << " Copy Create " << this << endl; } Int& operator=(const Int& it) { if (this != &it) value = it.value; cout << this << " operator= " << &it << endl; return *this; } // 移动 Int(Int&& it) :value(it.value) { it.value = -1; cout << &it << " Move Create " << this << endl; } Int& operator=(Int&& it) { if (this != &it) { value = it.value; it.value = -1; } cout << this << " Move operator= " << &it << endl; return *this; } // 访问 void SetValue(int x) { value = x; } int GetValue() const { return value; } int& Value() { return value; } const int& Value() const { return value; } void Print() const { cout << "value: " << value << endl; } // 类型转换 operator int() const { return value; } // 运算符 Int operator+(const Int& it) const { return Int(value + it.value); } Int operator+(int x) const { return Int(value + x); } Int& operator++() { value++; return *this; } Int operator++(int) { return Int(value++); } Int& operator--() { value--; return *this; } Int operator--(int) { return Int(value--); } // 流 ostream& operator<<(ostream& out) const { out << value; return out; } istream& operator>>(istream& in) { in >> value; return in; } }; // 全局运算符 inline ostream& operator<<(ostream& out, const Int& it) { it << out; return out; } inline istream& operator>>(istream& in, Int& it) { in >> it.Value(); return in; } inline Int operator+(int x, const Int& it) { return it + x; }二、C++ 线程基础:创建、join、detach
2.1 线程创建(函数 / Lambda)
std::thread可以绑定:普通函数、函数对象、Lambda、成员函数。
#include <iostream> #include <thread> #include <mutex> #include <memory> #include "Int.hpp" using namespace std; void funa(int a) { cout << "thread funa: " << a << " | id: " << this_thread::get_id() << endl; } void funb(int a, int b) { cout << "thread funb: " << a << " " << b << endl; } int main() { thread t1(funa, 10); thread t2(funb, 10, 20); thread t3([](int x) { cout << "lambda: " << x << endl; }, 30); t1.join(); t2.join(); t3.join(); return 0; }关键点:
join():主线程等待子线程完成,安全回收资源必须 join 或 detach,否则线程析构时程序崩溃
2.2 detach 分离线程
void funa(int a) { this_thread::sleep_for(chrono::milliseconds(10)); cout << "detach thread run: " << a << endl; } int main() { thread t(funa, 100); t.detach(); // 分离,主线程不再等待 this_thread::sleep_for(chrono::milliseconds(20)); return 0; }注意:
detach 后线程独立运行,主线程退出 → 进程结束 → 子线程直接被杀死
极易出现悬空对象 / 野指针 / 内存泄漏,工程慎用
三、线程传参(最容易出错!)
thread传参默认值传递(拷贝)。
3.1 值传递(最安全)
void funa(Int it) { it.Print(); } int main() { Int a(10); thread t(funa, a); // 会拷贝 t.join(); return 0; }3.2 引用传递(必须用 std::ref)
void funa(Int& it) { it.SetValue(100); it.Print(); } int main() { Int a(10); thread t(funa, ref(a)); // 不加 ref 编译失败 t.join(); cout << "main: " << a.GetValue() << endl; return 0; }3.3 裸指针传递(危险!)
void funa(Int* p) { if (p) p->Print(); } int main() { thread t; { Int a(10); t = thread(funa, &a); } // a 已经销毁! t.join(); // 悬空指针,未定义行为 return 0; }3.4 智能指针传递(最推荐)
shared_ptr 值传递(自动管理生命周期)shared_ptr 引用传递(不增加计数,高效)
void funa(shared_ptr<Int> sp) { if(sp) sp->Print(); } int main() { thread t; { auto sp = make_shared<Int>(10); t = thread(funa, sp); // 拷贝,计数+1 } t.join(); // 安全 return 0; }shared_ptr 引用传递(不增加计数,高效)
void funa(const shared_ptr<Int>& sp) { if (sp) { sp->Print(); } } int main() { auto sp = make_shared<Int>(10); thread t(funa, ref(sp)); t.join(); return 0; }3.5 移动语义传递(无拷贝)
void funa(Int&& it) { it.Print(); } int main() { Int a(10); thread t(funa, move(a)); t.join(); return 0; }四、线程同步:mutex + lock_guard(解决数据竞争)
多线程同时读写共享变量 =数据竞争= 结果错乱。
必须用互斥锁保护临界区。
const int n = 10; const int m = 10; mutex mtx; void funa(char ch) { for (int i = 0; i < n; ++i) { lock_guard<mutex> lock(mtx); // 自动加锁解锁 for (int j = 0; j < m; ++j) { printf("%c ", ch); } printf("\n"); } printf("------------------------\n"); } int main() { thread th[5]; for (int i = 0; i < 5; ++i) { th[i] = thread(funa, 'A' + i); } for (auto& t : th) t.join(); return 0; }关键点:
lock_guard:RAII 风格,异常安全临界区越小越好
不要重复加锁、不要死锁
五、多线程高频坑点总结(必背)
thread 对象必须 join 或 detach
默认传参是拷贝,传引用必须
std::ref不要向线程传递局部对象指针 / 引用(会悬空)
优先使用 shared_ptr 管理线程对象生命周期
共享资源必须加锁,否则数据竞争
detach 极其危险,主线程退出会直接杀死子线程
不要在锁内做耗时操作,降低并发效率
移动语义可以避免拷贝,提升效率
六、整篇博客核心总结(高质量升华)
C++ 多线程编程的核心,其实就三件事:
线程生命周期管理:join /detach/ 智能指针托管
参数传递安全:值 / 移动语义 / 智能指针
共享资源同步:mutex /lock_guard/ 原子变量
