详细解释 std::thread t1(ThreadPrinter::print, printer, 1);
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class ThreadPrinter {
private:
int count;// 共享资源:当前数字
int max;// 共享资源:最大数字
bool turn; // true表示第一个线程打印,false表示第二个线程打印
std::mutex mtx;// 保护以上三个共享资源的锁
std::condition_variable cv;// 用于线程间通信的条件变量
public:
ThreadPrinter(int max) : count(1), max(max), turn(true) {}
void print(int threadId) {
while (count <= max) {
// 获取锁 - 关键步骤!
std::unique_lock<std::mutex> lock(mtx);
// 从这里开始,当前线程独占了共享资源:其他线程无法访问这些变量 count max turn
// 检查是否轮到当前线程
while ((threadId == 1 && !turn) || (threadId == 2 && turn)) {
//条件不满足,进入等待状态
cv.wait(lock);
/*
释放锁 mtx(让其他线程可以访问共享资源)
将线程置于等待状态(不消耗CPU)
将线程加入到条件变量的等待队列中
当其他线程调用 cv.notify_all() 时:
所有等待的线程被标记为可运行状态
线程尝试重新获取锁
获取到锁后,从 wait() 调用中返回
*/
}
// 执行打印工作(此时持有锁)
std::cout << "Thread " << threadId << ": " << count << std::endl;
count++;
// 切换控制权
turn = !turn;
// 通知另一个线程可以打印了 通知其他线程
cv.notify_all();
//锁在作用域结束时自动释放
// lock 析构函数被调用 → mtx.unlock()
}
}
};
int main() {
ThreadPrinter printer(100);
std::thread t1(&ThreadPrinter::print, &printer, 1);
std::thread t2(&ThreadPrinter::print, &printer, 2);
t1.join();
t2.join();
return 0;
}
这行代码创建了一个新线程,让我逐部分详细解释:
1. std::thread 类的基本概念
std::thread 是 C++11 引入的线程类,用于创建和管理线程。
基本语法:
cpp
std::thread thread_object(callable, arg1, arg2, ...);
2. 成员函数作为线程函数的特殊语法
当你想要让线程执行一个类的成员函数时,需要特殊的语法,因为成员函数有隐含的 this 指针。
错误的方式(不能这样写):
cpp
std::thread t1(printer.print, 1); // 错误!不能直接传递成员函数
正确的方式:
cpp
std::thread t1(&ThreadPrinter::print, &printer, 1);
3. 参数详解
让我们分解这个调用的三个参数:
参数1:&ThreadPrinter::print
-
这是成员函数指针
-
ThreadPrinter::表示这个函数属于ThreadPrinter类 -
&取地址操作符,获取函数的地址 -
print是要执行的成员函数名
为什么需要这样写?
因为成员函数在内存中的地址与普通函数不同,它需要与特定的对象实例关联。
参数2:&printer
-
这是对象实例的指针
-
&printer获取printer对象的地址 -
这个参数会成为成员函数的
this指针
相当于在说:"在 printer 这个对象上调用 print 方法"
参数3:1
-
这是传递给
print函数的参数 -
对应
print(int threadId)中的threadId参数
4. 底层工作原理
实际上,编译器会将这个调用转换为类似这样的形式:
cpp
// 伪代码:编译器生成的类似代码
void thread_function() {// 在 printer 对象上调用 print 方法,传递参数 1printer->print(1);
}std::thread t1(thread_function);
5. 其他等效写法
除了使用指针,还有几种其他写法:
使用引用:
cpp
std::thread t1(&ThreadPrinter::print, std::ref(printer), 1);
使用 lambda 表达式(更直观的写法):
cpp
std::thread t1([&printer]() {printer.print(1);
});
使用 std::bind:
cpp
auto bound_function = std::bind(&ThreadPrinter::print, &printer, 1); std::thread t1(bound_function);
6. 为什么需要这样设计?
成员函数的特殊性:
cpp
class MyClass {
public:void memberFunction(int x) {// 实际上编译器会处理为:// void memberFunction(MyClass* this, int x)}
};
每个成员函数都有一个隐藏的 this 参数,指向调用它的对象。std::thread 需要知道:
-
要调用哪个函数(函数地址)
-
在哪个对象上调用(this 指针)
-
函数的参数是什么(普通参数)
7. 完整的执行流程
cpp
int main() {// 1. 创建 ThreadPrinter 对象ThreadPrinter printer(100);// 2. 创建线程 t1// 语法:std::thread(成员函数指针, 对象指针, 函数参数)std::thread t1(&ThreadPrinter::print, &printer, 1);// 等效于:在后台线程中执行 printer.print(1);// 3. 创建线程 t2 std::thread t2(&ThreadPrinter::print, &printer, 2);// 等效于:在另一个后台线程中执行 printer.print(2);// 4. 等待两个线程完成t1.join(); // 主线程等待 t1 完成t2.join(); // 主线程等待 t2 完成return 0;
}
8. 重要注意事项
对象生命周期:
cpp
// 危险!对象可能在线程使用前就被销毁了
void dangerous_example() {ThreadPrinter local_printer(100);std::thread t(&ThreadPrinter::print, &local_printer, 1);// 函数结束,local_printer 被销毁,但线程可能还在运行!// 应该先 join() 再让对象离开作用域t.join();
}
线程安全:
-
多个线程访问同一个对象需要同步机制(这正是代码中使用 mutex 的原因)
-
如果没有适当的同步,会出现数据竞争和未定义行为
总结
std::thread t1(&ThreadPrinter::print, &printer, 1); 这行代码的意思是:
"创建一个新线程,在这个线程中,调用 printer 对象的 print 成员函数,并传递参数 1"
这种语法是 C++ 中启动成员函数线程的标准方式,需要同时提供:
-
成员函数地址
-
对象实例指针
-
函数参数
理解了这种语法后,你就能在 C++ 中轻松地创建执行类成员函数的线程了。
t1.join()
1. 基本概念
join() 是什么?
join() 是 std::thread 类的一个成员函数,它的作用是:让当前线程(主线程)等待被调用的线程(t1)执行完成。
字面意思理解
-
join 在英文中是"加入"的意思
-
可以理解为:把子线程"加入"到主线程中,等待它完成后再继续
2. join() 的详细工作机制
没有 join() 的情况:
cpp
int main() {ThreadPrinter printer(100);std::thread t1(&ThreadPrinter::print, &printer, 1);std::thread t2(&ThreadPrinter::print, &printer, 2);// 如果没有 join(),主线程会立即继续执行std::cout << "Main thread continues immediately" << std::endl;return 0; // 程序退出,可能中断正在运行的 t1 和 t2
}
问题:主线程可能在 t1、t2 完成前就结束了,导致:
-
子线程被强制终止
-
资源泄漏
-
未定义行为
有 join() 的情况:
cpp
int main() {ThreadPrinter printer(100);std::thread t1(&ThreadPrinter::print, &printer, 1);std::thread t2(&ThreadPrinter::print, &printer, 2);std::cout << "Main thread created t1 and t2" << std::endl;// 主线程在这里阻塞,等待 t1 完成t1.join();std::cout << "Thread t1 has finished" << std::endl;// 主线程在这里阻塞,等待 t2 完成 t2.join();std::cout << "Thread t2 has finished" << std::endl;std::cout << "All threads completed, main thread exiting" << std::endl;return 0;
}
3. join() 的执行流程
时间线示意图:
text
时间轴: 0s --- 1s --- 2s --- 3s --- 4s 主线程: 创建t1,t2 → 等待t1 → 等待t2 → 结束 线程t1: 开始 → 执行中 → 完成 线程t2: 开始 → 执行中 → 完成
具体执行步骤:
cpp
int main() {// 步骤1:主线程创建 printer 对象ThreadPrinter printer(100);// 步骤2:主线程创建 t1 和 t2,它们开始异步执行std::thread t1(&ThreadPrinter::print, &printer, 1);std::thread t2(&ThreadPrinter::print, &printer, 2);// 此时有三个线程在并发执行:主线程、t1、t2// 步骤3:主线程调用 t1.join()t1.join();// 主线程在这里阻塞,直到 t1 的 print 函数执行完毕// t1 完成后,主线程继续执行// 步骤4:主线程调用 t2.join() t2.join();// 主线程再次阻塞,等待 t2 完成// 步骤5:所有线程都完成后,主线程继续return 0;
}
4. join() 的底层原理
操作系统层面的工作:
-
线程状态检查:检查目标线程是否还在运行
-
线程同步:如果目标线程还在运行,当前线程进入等待状态
-
资源清理:当目标线程完成后,清理线程资源
-
状态更新:标记线程为已完成状态
伪代码实现:
cpp
class thread {
private:native_handle_type native_handle; // 操作系统线程句柄public:void join() {if (this_thread == native_handle) {throw std::runtime_error("Cannot join self");}if (already_joined) {throw std::runtime_error("Thread already joined");}// 调用操作系统API等待线程结束os_wait_for_thread_completion(native_handle);// 清理资源cleanup_thread_resources();already_joined = true;}
};
5. join() 的重要特性
1. 一次性操作
cpp
std::thread t1(&ThreadPrinter::print, &printer, 1);t1.join(); // 正确:等待t1完成 // t1.join(); // 错误:不能再次join,会抛出std::system_error
2. 线程完成检测
cpp
std::thread t1(&ThreadPrinter::print, &printer, 1);// 检查线程是否可join(是否还在运行)
if (t1.joinable()) {t1.join(); // 线程还在运行,等待它
} else {// 线程已经完成或被detach
}
3. 异常安全考虑
cpp
// 不安全的写法
std::thread t1(&ThreadPrinter::print, &printer, 1);
// ... 如果这里发生异常,t1 可能没有被 join
t1.join();// 安全的写法 - 使用 RAII
class ThreadGuard {std::thread& t;
public:explicit ThreadGuard(std::thread& t_) : t(t_) {}~ThreadGuard() {if (t.joinable()) {t.join();}}
};std::thread t1(&ThreadPrinter::print, &printer, 1);
ThreadGuard guard(t1); // 析构时自动join
6. join() 的替代方案
detach() - 分离线程
cpp
std::thread t1(&ThreadPrinter::print, &printer, 1); t1.detach(); // 分离线程,主线程不等待它// 分离后: // - 线程在后台独立运行 // - 不能再调用 join() // - 线程结束时资源自动回收
使用场景:
-
后台任务(如日志记录、监控)
-
不需要等待结果的任务
std::async - 更高级的异步操作
cpp
#include <future>// 异步执行,返回 future
auto future = std::async(std::launch::async, []() {return some_computation();
});// 需要结果时等待
auto result = future.get(); // 类似 join(),但可以获取返回值
7. 在实际代码中的执行示例
让我们在你的代码基础上添加一些输出来观察 join() 的行为:
cpp
int main() {ThreadPrinter printer(10); // 只打印到10,方便观察std::cout << "Main: Creating thread t1" << std::endl;std::thread t1(&ThreadPrinter::print, &printer, 1);std::cout << "Main: Creating thread t2" << std::endl;std::thread t2(&ThreadPrinter::print, &printer, 2);std::cout << "Main: About to call t1.join()" << std::endl;t1.join();std::cout << "Main: t1.join() returned - t1 is done" << std::endl;std::cout << "Main: About to call t2.join()" << std::endl;t2.join();std::cout << "Main: t2.join() returned - t2 is done" << std::endl;std::cout << "Main: All threads completed, exiting" << std::endl;return 0;
}
可能的输出:
text
Main: Creating thread t1 Main: Creating thread t2 Main: About to call t1.join() Thread 1: 1 Thread 2: 2 Thread 1: 3 Thread 2: 4 ... Thread 2: 10 Main: t1.join() returned - t1 is done Main: About to call t2.join() Main: t2.join() returned - t2 is done // t2可能已经提前完成 Main: All threads completed, exiting
8. 总结
t1.join() 的核心作用:
-
同步控制:让主线程等待子线程完成
-
资源管理:确保线程资源被正确清理
-
程序稳定性:防止主线程提前退出导致子线程异常终止
重要规则:
-
每个
std::thread对象在析构前必须要么join()要么detach() -
join()只能调用一次 -
调用
join()后,线程对象不再代表活跃的线程
理解 join() 是掌握 C++ 多线程编程的基础,它确保了线程间的有序协作和资源的正确管理。
线程在 std::thread 构造函数被调用时立即开始执行,不是在 join() 时才开始!
join只是主线程在等待他们执行结束,阻塞主线程,防止主线程提前结束
