当前位置: 首页 > news >正文

详细解释 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 需要知道:

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

http://www.dtcms.com/a/525141.html

相关文章:

  • 云建站的正确步骤客户关系crm管理系统
  • RocketMQ核心技术精讲-----初识RocketMQ与快速上手
  • 青岛的互联网公司有哪些西安做网站优化
  • 香橙派双雄:OPi 6 Plus与4 Pro,以差异化战略切割边缘AI市场
  • openai-cookbook:what makes documentation good(翻译总结)
  • 智能网联汽车网络发展需求与模式分析:面向2030年的核心逻辑
  • java transient关键字有什么用
  • 免费建站哪个比较好大学 生免费商业网站设计
  • perl网站开发企业培训内容有哪些
  • 医疗信创的里程碑:浙江省人民医院异构多活容灾架构的突破与启示
  • KingbaseES数据库:首个多院区异构多活容灾架构,浙人医创新开新篇
  • 标注可用于IP≠实战可用——超50%的IP抗体实际效果欠佳,如何实现0风险IP实验?
  • 建设人才证书查询网站做网站的公司北京有哪些
  • python with 语法
  • tlv32aic32 外部DAC的I2S音频流运行过程分析
  • I/V自动曲线量测仪的主要功能、测量方法和应用
  • 什么是电子负载?爱科赛博电子负载应用探讨
  • 2025.10.24总结
  • 邯郸哪里做网站优化thinkphp企业网站源码
  • BUYCOIN:以社区共治重构加密交易版图,定义交易所3.0时代
  • 建立平台网站需要花多少钱国贸附近网站建设
  • 【Linux C/C++开发】epoll模式的开源库及原生socket实现
  • ARP 报文和 IP 数据报的 区分与联系
  • html网站开发目标临沂做网站多少钱
  • 代谢组学之新手入门级知识概览
  • 开关电源拓扑工程宝典:从原理到实战的深度设计指南
  • 深度学习SE,CBAM,ECA,SimAM模块汇总之SE
  • 10. Python 列表:从单元素更新到切片批量处理
  • 气凝胶基复合相变材料研究进展
  • 天门市网站建设seowordpress小说插件