详细解释 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只是主线程在等待他们执行结束,阻塞主线程,防止主线程提前结束

相关推荐
β添砖java6 小时前
vivo响应式官网
前端·css·html·1024程序员节
er_rui6 小时前
U167571 信使
1024程序员节
成震19716 小时前
esp-idf 最简操作
1024程序员节
喜葵7 小时前
微信小程序第三方代开发模式技术调研与实践
1024程序员节
云起SAAS7 小时前
空号号码状态检测抖音快手微信小程序看广告流量主开源
ai编程·1024程序员节·看广告变现轻·空号号码状态检测
MarkHD7 小时前
Dify从入门到精通 第27天 在Dify中构建天气查询机器人
1024程序员节
진영_7 小时前
深度学习打卡第TR5周:Transformer实战:文本分类
1024程序员节
傻童:CPU8 小时前
C语言需要掌握的基础知识点之线性表
c语言·1024程序员节
Heavy sea8 小时前
Linux串口应用编程
linux·c语言·1024程序员节