学懂C++(二十四):高级教程——C++ 多线程编程中 std::thread 的深入详解

在 C++11 中,std::thread 提供了对于多线程编程的强大支持。本节内容将详细探讨 std::thread 的使用,包括构造函数、成员函数、参数传递的注意事项等,同时结合示例进行深入讲解。

一、头文件

要使用 std::thread,需要包含以下头文件:

cpp 复制代码
#include <iostream> // 用于输入输出操作
#include <thread>   // 用于线程支持
#include <mutex>    // 用于线程间的同步(可选)
#include <vector>   // 用于容器支持(可选)
#include <memory>   // 用于智能指针(可选)

具体来说:

  • #include <iostream>:用于输入输出操作,通常用于打印信息。
  • #include <thread>:这是使用 std::thread 所必需的头文件,提供了多线程支持。
  • #include <mutex>:用于定义互斥量(std::mutex)等同步机制,以控制线程之间的访问。
  • #include <vector>:常用于存储线程或者其他动态数组的容器,虽然不是 std::thread 必需的,但在多线程编程中经常使用。
  • #include <memory>:提供智能指针(如 std::shared_ptrstd::unique_ptr),在多线程环境中管理动态内存时非常有用。

二、std::thread 构造函数

std::thread 的构造函数用于创建一个新线程并执行指定的可调用对象(如函数、Lambda 表达式等)。

示例 1:基本的线程创建

cpp 复制代码
#include <iostream>
#include <thread>

void threadFunction() {
    std::cout << "Thread is running!" << std::endl;
}

int main() {
    std::thread t(threadFunction); // 创建线程并执行 threadFunction

    if (t.joinable()) { // 检查线程是否可以被加入
        t.join(); // 等待线程完成
    }

    std::cout << "Thread has finished." << std::endl;
    return 0;
}

输出结果

cpp 复制代码
Thread is running!
Thread has finished.

三、其他成员函数

1. join()

用于等待线程完成。

2. joinable()

检查线程是否可以被加入。

3. detach()

将线程与主线程分离,允许线程在后台运行。

示例 2:使用 detach()

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

void threadFunction() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Detached thread is running!" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.detach(); // 使线程与主线程分离

    std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待一段时间
    std::cout << "Main thread is finishing." << std::endl;
    return 0;
}

输出结果

cpp 复制代码
Detached thread is running!
Main thread is finishing.

四、传递临时参数作为线程对象的注意事项

4.1 解决办法

在创建线程时传递临时参数时,需要注意临时对象的生命周期问题。

示例 3:传递临时参数错误示例

cpp 复制代码
#include <iostream>
#include <thread>

void printMessage(std::string msg) {
    std::cout << msg << std::endl;
}

int main() {
    std::thread t(printMessage, "Hello from thread!"); // 传递临时参数

    t.join(); // 等待线程完成
    return 0;
}

输出结果

cpp 复制代码
Hello from thread!

4.2 原因分析

虽然上述程序在此情况下运行正常,但如果将其改为使用临时对象的引用,则会出现问题:

示例 4:传递临时参数的引用错误示例

cpp 复制代码
#include <iostream>
#include <thread>

void printMessage(const std::string& msg) {
    std::cout << msg << std::endl;
}

int main() {
    std::string temp = "Hello from thread!";
    std::thread t(printMessage, std::move(temp)); // 使用 std::move 传递

    t.join(); // 等待线程完成
    // std::cout << temp << std::endl; // 这里会导致未定义行为
    return 0;
}

4.3 总结

在创建线程时,避免直接传递临时对象的引用。可以使用 std::shared_ptrstd::unique_ptr 来确保对象的生命周期。

五、传递类对象、智能指针作为线程参数

5.1 修改子线程中的对象,不会影响主线程中的对象

如果向线程传递对象的拷贝,当在子线程中修改对象时,主线程中的对象不会受到影响。

示例 5:传递类对象

cpp 复制代码
#include <iostream>
#include <thread>

class Counter {
public:
    void increment() {
        for (int i = 0; i < 5; ++i) {
            ++value;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }

    int getValue() const {
        return value;
    }

private:
    int value = 0;
};

int main() {
    Counter counter;
    std::thread t(&Counter::increment, counter); // 传递对象的拷贝

    t.join(); // 等待线程完成
    std::cout << "Main thread counter value: " << counter.getValue() << std::endl; // 值为 0
    return 0;
}
输出结果
cpp 复制代码
Main thread counter value: 0

5.2 传递智能指针

使用智能指针可以在多个线程间共享同一对象。

示例 6:使用 std::shared_ptr

cpp 复制代码
#include <iostream>
#include <thread>
#include <memory>

class Counter {
public:
    void increment() {
        for (int i = 0; i < 5; ++i) {
            ++value;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }

    int getValue() const {
        return value;
    }

private:
    int value = 0;
};

int main() {
    auto counter = std::make_shared<Counter>(); // 创建共享指针
    std::thread t(&Counter::increment, counter); // 传递智能指针

    t.join(); // 等待线程完成
    std::cout << "Main thread counter value: " << counter->getValue() << std::endl; // 值为 5
    return 0;
}

输出结果

cpp 复制代码
Main thread counter value: 5

六、std::this_thread 命名空间中相关辅助函数介绍

std::this_thread 命名空间提供了一些与当前线程相关的辅助函数。

1. sleep_for()

使当前线程睡眠指定的时间。

示例 7:使用 sleep_for()

cpp 复制代码
#include <iostream>
#include <thread>

void threadFunction() {
    std::cout << "Thread will sleep for 2 seconds..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 使线程睡眠
    std::cout << "Thread awakens!" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.join(); // 等待线程完成
    return 0;
}

输出结果

cpp 复制代码
Thread will sleep for 2 seconds...
Thread awakens!

2. sleep_until()

使当前线程睡眠直到指定的时间点。

示例 8:使用 sleep_until()

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

void threadFunction() {
    auto wakeUpTime = std::chrono::steady_clock::now() + std::chrono::seconds(2);
    std::cout << "Thread will sleep until a specific time..." << std::endl;
    std::this_thread::sleep_until(wakeUpTime); // 睡眠直到指定时间
    std::cout << "Thread awakens!" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.join(); // 等待线程完成
    return 0;
}

输出结果

cpp 复制代码
Thread will sleep until a specific time...
Thread awakens!

3. get_id()

获取当前线程的 ID。

示例 9:使用 get_id()

cpp 复制代码
#include <iostream>
#include <thread>

void printThreadId() {
    std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}

int main() {
    std::thread t(printThreadId);
    t.join(); // 等待线程完成
    return 0;
}

输出结果

cpp 复制代码
Thread ID: 140736910476544

(线程 ID 会因运行环境而异。)

总结

本文详细探讨了 C++ 多线程编程中的 std::thread,包括构造函数、成员函数、参数传递的注意事项、类对象和智能指针的使用,以及 std::this_thread 命名空间中的辅助函数。通过这些示例,希望能够帮助您更深入地理解 C++ 中的多线程编程,并在实际开发中灵活应用。

相关推荐
爬虫程序猿3 小时前
用 Python 给京东商品详情做“全身 CT”——可量产、可扩展的爬虫实战
开发语言·爬虫·python
徐同保5 小时前
tailwindcss暗色主题切换
开发语言·前端·javascript
蓝纹绿茶5 小时前
bash:**:pip:***python: 错误的解释器: 没有那个文件或目录
开发语言·python·pip
AA陈超5 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P06-14 属性菜单 - 文本值行
c++·游戏·ue5·游戏引擎·虚幻
云知谷5 小时前
【经典书籍】C++ Primer 第15章类虚函数与多态 “友元、异常和其他高级特性” 精华讲解
c语言·开发语言·c++·软件工程·团队开发
START_GAME6 小时前
深度学习Diffusers:用 DiffusionPipeline 实现图像生成
开发语言·python·深度学习
不爱编程的小九九6 小时前
小九源码-springboot088-宾馆客房管理系统
java·开发语言·spring boot
weixin_582985186 小时前
OpenCV cv::Mat.type() 以及类型数据转换
c++·opencv·计算机视觉
Evand J7 小时前
【MATLAB例程】到达角度定位(AOA),平面环境多锚点定位(自适应基站数量),动态轨迹使用EKF滤波优化。附代码下载链接
开发语言·matlab·平面·滤波·aoa·到达角度
细节控菜鸡7 小时前
【2025最新】ArcGIS for JS 实现随着时间变化而变化的热力图
开发语言·javascript·arcgis