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

C++ std::thread线程类 相关问题、函数总结

问题:

1、std::thread构造函数,可以接收一个函数体吗?
std::thread构造函数可以接收一个函数体作为可调用对象。
在C++11标准中,std::thread支持通过:
构造函数直接传递(函数、lambda表达式、函数指针)等可调用对象,【创建】并【启动】新线程。 ‌

2、std::thread什么时候启动?
从C++11开始,当你创建一个std::thread对象并传入一个函数时(对象的实例化),
这个线程会自动开始执行该函数。
因此,通常你不需要显式调用start()方法!
std::thread t(threadFunction); // 创建并启动线程

3、在join\detach之后,哪些函数还能调用,哪些不能再调用?

t2.detach(); // 线程分离,不会等待t2的线程函数执行完毕,主线程可以继续执行

在join\detach之后,t2是不可连接状态。
但是仍然可以调用get_id()\joinable()\等方法。不可再调用join\detach等方法。

join之后就是不可连接状态,即使move到新线程对象之后,也不行,t2也不能join。
t1 线程失去所有权,t1中的线程函数也就结束了,所以t1不会执行线程函数。
t2获得了t1的所有权,去执行t1的线程函数。

std::thread t1([] {std::cout << "Hello, world!" << std::endl;});
t1.join();
thread t2(move(t1));

4、线程数组中添加lambda表达式类型的线程元素,为何只能用emplace_back,而不用push_back()?


解释:
这个问题涉及C++中push_back和emplace_back 对lambda表达式 的不同处理方式。
以下是详细解释:
‌4.1 核心区别‌:
push_back:需要构造一个临时对象,然后进行【拷贝/移动】操作;
emplace_back:直接在容器内构造对象,避免了临时对象的创建;

‌4.2 具体原因分析‌:
‌1.lambda表达式的特性‌:
每个lambda表达式都有独特的匿名类型;
lambda不能直接赋值给另一个lambda变量(即使签名相同);

2.push_back失败的原因‌:
push_back需要先创建一个临时lambda对象;
然后尝试将这个临时对象【拷贝/移动】到容器中;
lambda表达式的【拷贝构造函数被删除】,不能直接赋值(因为lambda本质上是匿名类对象);

3.emplace_back成功的原因‌:
emplace_back直接在容器内构造lambda对象;
避免了拷贝/移动操作
emplace_back【接受lambda表达式】作为参数并就地构造;

5、C++ thread和 winAPI\linux中的API区别?

std::thread 提供了一个方便的方式来实现多线程编程,使得开发者能够充分利用现代多核处理器的能力。通过灵活的线程管理和同步机制,可以有效地实现并发计算。

5.1 在 Linux 和其他 Unix-like 系统上使用 std::thread 时,通常需要链接 pthread 库,因为 C++11 的 std::thread 是基于 POSIX 线程库(pthread)实现的。因此,当编译一个使用 std::thread 的 C++ 程序时,需要在编译命令中添加 -lpthread 选项,比如:

g++ -std=c++11 -o my_program my_program.cpp -lpthread

在使用某些高级的编译器(例如 g++ 版本 5 及更高版本)时,如果使用了 -std=c++11 或者更高版本的选项,编译器可能会自动链接 pthread 库,因此即使没有明确指定 -lpthread,编译也能成功。

5.2 在 Windows 系统上,使用 std::thread 时不需要链接 pthread,因为 Windows 有自己实现的线程库,std::thread 使用的是 Windows API。

=》总之,std::thread 是跨平台的,在不同的平台,其实是调用了各自封装的API函数!

篇一、

std::thread 是 C++11 标准库中的一个类,用于支持多线程编程。它提供了一些成员函数来管理线程的生命周期和与线程进行交互。

std::thread 的一些主要成员函数:

joinable(): 检查线程是否可以被 join。
get_id(): 获取线程的 ID。
native_handle(): 获取与实现相关的本机句柄。
hardware_concurrency(): 返回实现的并发级别(硬件支持的最大并发线程数)。
join(): 阻塞调用线程,直到被调用线程完成。
detach(): 将线程与调用对象分离,允许线程在后台继续运行。
swap(): 交换两个 std::thread 对象的状态。

展示一个例子

首先输出了硬件支持的最大并发线程数。然后创建了四个线程,并展示了如何获取它们的 ID、检查它们是否可以被 join、获取它们的 native handle 以及交换线程。最后,我们等待前两个线程结束并把后两个线程设置为脱离状态。

#include <iostream>
#include <thread>
#include <chrono>// 定义一个简单的函数,线程将执行该函数
void my_function(int id) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作std::cout << "Thread " << id << " finished execution." << std::endl;
}int main() {// 获取硬件支持的最大并发线程数std::size_t max_threads = std::thread::hardware_concurrency();std::cout << "Maximum number of concurrent threads supported by hardware: "<< max_threads << std::endl;// 创建多个线程std::thread t1(my_function, 1);std::thread t2(my_function, 2);// 输出线程的 IDstd::cout << "Thread 1 ID: " << t1.get_id() << std::endl;std::cout << "Thread 2 ID: " << t2.get_id() << std::endl;// 检查线程是否可 joinif (t1.joinable()) {std::cout << "Thread 1 is joinable." << std::endl;} else {std::cout << "Thread 1 is not joinable." << std::endl;}// 输出底层线程句柄(具体实现依赖于平台)void* handle1 = t1.native_handle();void* handle2 = t2.native_handle();std::cout << "Native handle for thread 1: " << handle1 << std::endl;std::cout << "Native handle for thread 2: " << handle2 << std::endl;// 交换线程std::thread t3(my_function, 3);std::thread t4(my_function, 4);t3.swap(t4);std::cout << "Swapped thread 3 and 4." << std::endl;// 输出交换后的线程 IDstd::cout << "Thread 3 ID after swap: " << t3.get_id() << std::endl;std::cout << "Thread 4 ID after swap: " << t4.get_id() << std::endl;// 等待线程 t1 和 t2 结束std::cout << "Waiting for threads 1 and 2 to finish..." << std::endl;t1.join();t2.join();// 设置线程 t3 和 t4 为脱离状态std::cout << "Detaching threads 3 and 4..." << std::endl;t3.detach();t4.detach();std::cout << "Main function completed." << std::endl;return 0;
}

输出

Maximum number of concurrent threads supported by hardware: 20
Thread 1 ID: 16628
Thread 2 ID: 9456
Thread 1 is joinable.
Native handle for thread 1: 00000000000000C4
Native handle for thread 2: 00000000000000C8
Swapped thread 3 and 4.
Thread 3 ID after swap: 10992
Thread 4 ID after swap: 22592
Waiting for threads 1 and 2 to finish...
Thread 1 finished execution.Thread 4Thread 2 finished execution.
Thread 3 finished execution. finished execution.Detaching threads 3 and 4...
Main function completed.


join() 说明

join() 方法用于等待线程完成其任务。当调用 join() 方法时,调用线程(通常是主线程)会被阻塞,直到被 join 的线程完成为止。

关于何时线程不可 join,有以下几种情况:

线程尚未启动:
如果线程对象尚未被启动,那么它是不可 join 的。例如,如果创建了一个 std::thread 对象但没有传递任何函数给它,或者还没有调用 start() 方法(注意:std::thread 没有 start() 方法,线程是在构造时立即启动的)。

线程已经结束:
如果线程已经完成了它的任务,并且线程对象已经被 join 或者 detach,那么它是不可 join 的。这是因为线程资源已经被释放。

线程已经被 detach:
如果线程被 detach() 方法分离,那么它将不会被 join。这意味着线程将在完成后自动清理资源,而不需要调用 join() 方法。一旦线程被 detach,它就不再与 std::thread 对象关联,因此也不可再 join。

线程对象已经销毁:
如果 std::thread 对象已经超出作用域并被销毁,那么它是不可 join 的。在这种情况下,线程会被自动 join 或 detach,具体取决于线程的状态。

输出

Thread 1 ID: 23612
Thread 2 ID: 2940
Thread 1 is joinable.
Waiting for threads 1 and 2 to finish...
Thread 2 finished execution.
Thread 1 finished execution.
Threads 1 and 2 have been joined.
Thread 3 is not joinable.
Thread 4 is not joinable.

detach()说明

调用 detach 函数之后:1. *this 不再代表任何的线程执行实例。2. joinable() == false3. get_id() == std::thread::id()

detach() 方法用于将线程设置为脱离状态。脱离状态的线程会在完成后自动清理资源,不需要其他线程调用 join() 方法。这在以下场景中是有用的:

后台任务:如果有一些后台任务,如日志记录、数据同步等,这些任务可以在主线程退出后继续运行,这时可以使用 detach() 方法。

非阻塞操作:如果希望主线程不等待子线程完成,而是继续执行其他任务,可以使用 detach()。

线程池:在实现线程池时,通常会将线程设置为脱离状态,以便线程可以自动回收资源。

动态调整线程分配
如何使用 std::thread::swap 方法来动态地重新分配任务到线程。swap 方法确保了线程对象的状态被正确交换,而不需要重新创建或销毁线程

初始化线程池:首先创建了一个包含 numThreads 个线程的向量,并为每个线程分配一个任务。

动态调整线程分配:循环遍历线程池中的每个线程,对于每个线程,等待它完成当前任务后,如果还有新的任务要执行,就创建一个新的线程对象,并使用 swap 方法将新任务移到原来的线程对象上。

确保所有线程完成任务:在完成所有任务调整后,再次循环遍历线程池,确保所有线程都已完成它们的任务。

输出所有任务完成的消息:最后输出一条消息,表示所有任务都已经完成。

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <mutex>std::mutex mtx; // 用于输出的互斥量void taskFunction(int taskID) {std::this_thread::sleep_for(std::chrono::seconds(1));std::lock_guard<std::mutex> lock(mtx);std::cout << "Task " << taskID << " is complete." << std::endl;
}int main() {const int numThreads = 4;std::vector<std::thread> threadPool(numThreads);// 启动初始任务for (int i = 0; i < numThreads; ++i) {threadPool[i] = std::thread(taskFunction, i + 1);}// 动态调整线程分配for (int i = 0; i < numThreads; ++i) {if (threadPool[i].joinable()) {// 等待当前线程完成任务threadPool[i].join();// 如果还有新的任务,将任务移动到该线程上if (i + numThreads < 2 * numThreads) {std::thread newThread(taskFunction, i + numThreads + 1);// 确保新线程已经启动newThread.swap(threadPool[i]);}}}// 确保所有线程完成任务for (int i = 0; i < numThreads; ++i) {if (threadPool[i].joinable()) {threadPool[i].join();}}std::cout << "All tasks are complete." << std::endl;return 0;
}

输出

Task 1 is complete.
Task 2 is complete.
Task 3 is complete.
Task 4 is complete.
Task 7 is complete.
Task 8 is complete.
Task 6 is complete.
Task 5 is complete.
All tasks are complete.

————————————————
 原文链接:https://blog.csdn.net/flyfish1986/article/details/140792370

篇二、

C++11 的线程管理

C++11 引入了全面的多线程支持,使并发编程成为语言标准的一部分。核心组件包括线程管理(std::thread)、原子操作(std::atomic)和同步机制(互斥锁、条件变量)。

线程管理类 std::thread,用于创建和管理操作系统线程。它提供了跨平台的线程操作接口,消除了对平台特定 API(如 POSIX pthreads 或 Windows Threads)的依赖。

线程创建

我们可以通过传递可调用对象来构造 std::thread 线程实例,比如函数、Lambda、函数对象:

#include <thread>// 函数形式
void print(int num, const std::string& str) {std::cout << num << " " << str << "\n";
}// Lambda 形式
auto lambda = [](float f) { /*...*/ };int main() {// 创建线程并立即执行std::thread t1(print, 42, "Hello");  // 值传递std::string msg = "World";std::thread t2([&msg]() {            // 引用捕获std::cout << msg << "\n";});t1.join();t2.join();
}

当我们构造 std::thread 实例时,可以使用以下几种数据传递机制:

  • 默认行为:参数按值拷贝
  • 传递引用:需使用 std::ref 包装
  • 移动语义:使用 std::move 避免拷贝
void process_data(const BigData& data); // 大对象BigData data;
std::thread t1(process_data, data);       // 拷贝data(可能昂贵)
std::thread t2(process_data, std::ref(data)); // 传递引用
std::thread t3(process_data, std::move(data)); // 移动语义

另外,我们需要注意的线程异常隔离问题,因为线程内异常不会传播到主线程,所以我们必须在每个线程内部处理异常:

std::thread t([] {try {// 可能抛出异常的代码} catch (...) {// 处理所有异常}
});

线程生命周期的管理

我们可以通过以下操作对线程的生命周期进行管理:

  • 等待线程完成(join)
  • 分离线程(detach)
  • RAII 包装器(推荐模式)

在 std::thread 的生命周期管理中,"可连接"(joinable)和 "不可连接"(non-joinable)是核心概念,直接关系到线程的安全管理和资源回收。

1. 状态定义

  • 可连接状态 表示线程对象关联着一个活跃的或已结束但未清理的执行线程。可连接状态的条件有:
    1)通过构造函数创建了新线程
    2)尚未调用 join() 或 detach()
  • 不可连接状态 表示线程对象不再关联任何执行线程。不可连接状态的条件有:
    1)已调用 join()
    2)已调用 detach()
    3)被移动(所有权转移)
    4)默认构造(无关联线程)

2. 状态转换

a. 进入可连接状态

// 创建线程对象 → 可连接状态
std::thread t([]{ /* 执行任务 */ });  // ✅ joinable() == true

b. 转为不可连接状态

// 方式1: 调用 join()
t.join();  // ❗ joinable() == false// 方式2: 调用 detach()
t.detach();  // ❗ joinable() == false// 方式3: 移动所有权
std::thread t2 = std::move(t);  // t变为不可连接,t2变为可连接

3. 使用规则与后果

1)析构时必须不可连接

{std::thread t([]{ /*...*/ });  // 可连接状态// 未调用 join/detach → 析构时终止程序!
} // ❌ 此处调用 std::terminate() 终止程序!

2)禁止重复操作

std::thread t([]{ /*...*/ });t.join();  // ✅ 转为不可连接t.join();  // ❌ 抛出 std::system_error
// 错误信息:"Invalid argument"

3)移动后的状态

std::thread t1([]{ /*...*/ });  // t1 可连接
std::thread t2 = std::move(t1); // t1 不可连接,t2 可连接t1.joinable();  // false
t2.joinable();  // true

4. 状态检测:joinable()

joinable()主要用于安全判断当前状态,避免重复操作:

std::thread t;if (t.joinable()) {  // 检查状态// 安全操作区域t.join();  // 或 t.detach()
}

典型使用场景:

void safe_thread_management() {std::thread t([]{ /*...*/ });try {// 可能抛出异常的代码} catch (...) {if (t.joinable()) t.join();  // 异常时安全清理throw;}// 正常流程if (t.joinable()) t.join();
}

5. 那么为什么需要这种设计?

首要原因是为了资源安全,操作系统线程是重要资源(通常占用 1-10 MB 内存),所以必须明确决定线程结束后的处理方式。

第二个原因是为了避免僵尸线程join() 是为了确保线程资源被回收,而detach() 是为了明确的放弃所有权。

第三个原因是为了防止未定义行为,比如,未同步的线程访问已销毁的局部变量;再比如,未回收线程导致资源泄漏。

6. 正确用法示例

理解并正确管理线程的可连接状态,是避免多线程程序崩溃和资源泄漏的基础,也是编写健壮并发代码的关键所在。

下面列出几个正确使用的线程周期管理的场景:

场景1:等待线程完成(join)

void process_data() {std::vector<int> results;std::thread worker([&]{ results = calculate(); // 耗时计算});// 必须等待结果完成worker.join();  // 阻塞直到计算完成use_results(results); // 安全使用结果
}

场景2:分离后台线程(detach)

void start_background_task() {std::thread([]{while (true) {// 周期性后台任务std::this_thread::sleep_for(1h);cleanup();}}).detach(); // ✅ 立即分离
}

场景3:RAII 自动管理

RAII(Resource Acquisition Is Initialization) 是 C++ 的核心设计理念,中文译为"资源获取即初始化"。这是一种利用对象生命周期来管理资源(内存、文件句柄、网络连接、线程等)的技术,确保资源在任何执行路径下都能被正确释放。

class ThreadRAII {std::thread t;enum class Action { Join, Detach };Action action;
public:ThreadRAII(std::thread&& t, Action a) : t(std::move(t)), action(a) {}~ThreadRAII() {if (t.joinable()) {if (action == Action::Join) t.join();else t.detach();}}// ... 禁止拷贝 ...
};// 使用示例
{ThreadRAII tr(std::thread([]{ /* 关键任务 */ }),ThreadRAII::Action::Join  // 退出作用域自动join);// 即使此处抛出异常,线程也会被正确清理
}

总结

特性说明
构造即启动线程在创建时立即开始执行
移动语义线程所有权可转移,不可复制
join/detach必须在线程析构前调用,否则程序终止
参数传递默认值传递,引用需用std::ref,大对象用std::move
异常隔离线程内异常不会传播到主线程
RAII模式推荐使用包装类管理线程生命周期

正确使用std::thread需要注意:

  1. 始终管理线程生命周期(join/detach)
  2. 谨慎处理共享数据(使用同步原语)
  3. 避免线程间悬垂引用
  4. 考虑性能影响和线程创建开销
  5. 优先使用高级抽象(如std::async)简化代码

篇三、std::this_thread

功能

含义

get_id

获取线程 ID(函数)

yield

让出CPU

sleep_until

睡眠到时间点(功能)

sleep_for

睡眠时间跨度(功能)

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

相关文章:

  • 单调队列深度解析(下)
  • 如何解决 ‘NoneType‘ object has no attribute ‘get‘问题
  • GA-BP遗传算法优化BP神经网络数据生成,采用SVM分类模型评估
  • LM317 芯片推荐电路中保护二极管工作原理
  • 教育科技内容平台的用户定位与产品方案:从需求到解决方案的精准匹配
  • prometheus UI 和node_exporter节点图形化Grafana
  • GaussDB 数据库架构师修炼(六) 集群工具管理-1
  • 农经权二轮延包—批量出图片
  • 了解.NET Core状态管理:优化技巧与常见问题解决方案
  • 第4章 数据的排序、筛选和分类汇总
  • 金融系统AIGC能力中心赋能实战指南
  • 告别 T+1!解密金融级实时数据平台的构建与实践
  • RK3568 Linux驱动学习——SDK安装编译
  • 浅谈Rust语言特性
  • [C/C++安全编程]_[中级]_[如何避免出现野指针]
  • MySQL 写入性能优化全攻略(附 GitHub 面试题项目链接)
  • 相机参数的格式与作用
  • 大语言模型置信度增强实战指南
  • 第 3 篇:《WHERE 就是刷选项——像点外卖一样精确筛房!》
  • 【硬件】嵌入式问题
  • FPGA相关通信问题详解
  • XSS漏洞总结
  • 商业秘密视域下计算机软件的多重保护困境
  • 正点原子stm32F407学习笔记9——PWM 输出实验
  • 深度学习中的模型剪枝工具Torch-Pruning的使用
  • HAMR硬盘高温写入的可靠性问题
  • RHCE(2)
  • Qt Graphs 模块拟取代 charts 和 data visualization还有很长的路要走
  • 完美解决 Ubuntu 中自定义启动器图标重复的问题(以 MATLAB 为例)
  • 游戏开发日志