详细解释 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 方法,传递参数 1
    printer->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 需要知道:

  1. 要调用哪个函数(函数地址)

  2. 在哪个对象上调用(this 指针)

  3. 函数的参数是什么(普通参数)

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() 的底层原理

操作系统层面的工作:

  1. 线程状态检查:检查目标线程是否还在运行

  2. 线程同步:如果目标线程还在运行,当前线程进入等待状态

  3. 资源清理:当目标线程完成后,清理线程资源

  4. 状态更新:标记线程为已完成状态

伪代码实现:

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() 的核心作用:

  1. 同步控制:让主线程等待子线程完成

  2. 资源管理:确保线程资源被正确清理

  3. 程序稳定性:防止主线程提前退出导致子线程异常终止

重要规则

  • 每个 std::thread 对象在析构前必须要么 join() 要么 detach()

  • join() 只能调用一次

  • 调用 join() 后,线程对象不再代表活跃的线程

理解 join() 是掌握 C++ 多线程编程的基础,它确保了线程间的有序协作和资源的正确管理。

线程在 std::thread 构造函数被调用时立即开始执行 ,不是在 join() 时才开始!

join只是主线程在等待他们执行结束,阻塞主线程,防止主线程提前结束

相关推荐
学传打活2 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续3)
微信公众平台·1024程序员节·汉字·昆仑正义文化
学传打活7 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续1)
微信公众平台·1024程序员节·汉字·昆仑正义文化
fangzhanpeng16815 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心17 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u01030552723 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u01030552724 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝25 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u01030552725 天前
Java图像处理实战指南
人工智能·1024程序员节
u0103055271 个月前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u0103055271 个月前
Java实用类应用精讲
人工智能·1024程序员节