只是想知道如何以及为什么?泰:)
#include <thread>
#include <string>
#include <iostream>
#include <format>
#include <utility>
#include <vector>
#include <memory>
class MyTask {
public:
explicit MyTask(std::string id)
: id_(std::move(id)),
thread_(&MyTask::Process, this) {}
~MyTask() {
if (thread_.joinable())
thread_.join();
}
void Process() {
std::cout << std::format("task {} running ...\n", id_);
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << std::format("task {} finished ...\n", id_);
}
private:
std::string id_;
std::thread thread_;
};
void ThreadPtrContainerTest() {
std::vector<std::unique_ptr<MyTask>> tasks;
for (int i = 1; i <= 3; ++i) {
auto task_id = std::to_string(i);
tasks.emplace_back(std::make_unique<MyTask>(task_id));
}
}
class MyTaskM {
public:
explicit MyTaskM(std::string id)
: id_(std::move(id)),
thread_(&MyTaskM::Process, this) {}
MyTaskM(MyTaskM &&rhs) noexcept
: id_(std::move(rhs.id_)),
thread_(std::move(rhs.thread_)) {}
MyTaskM &operator=(MyTaskM &&rhs) noexcept {
if (this != &rhs) {
id_ = std::move(rhs.id_);
thread_ = std::move(rhs.thread_);
}
return *this;
}
~MyTaskM() {
if (thread_.joinable())
thread_.join();
}
void Process() {
std::cout << std::format("task {} running ...\n", id_);
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << std::format("task {} finished ...\n", id_);
}
private:
std::string id_;
std::thread thread_;
};
void ThreadContainerTest() {
std::vector<MyTaskM> tasks;
for (int i = 1; i <= 3; ++i) {
auto task_id = std::to_string(i);
tasks.emplace_back(task_id);
}
}
int main() {
ThreadContainerTest();
/*
* run into void Process():
* std::cout << std::format("task {} running ...\n", id_);
* Error: SIGSEGV (Segmentation fault)
*
*/
ThreadPtrContainerTest();
/*
* work fine
*/
return 0;
}
您的线程绑定到创建它们的原始对象。如果移动该对象,线程仍绑定到旧对象。不仅如此,您还可以在没有同步的情况下移动。这两件事都是不好的。您需要修改您的设计。
主要问题在这里:
std::vector<MyTaskM> tasks;
for (int i = 1; i <= 3; ++i) {
auto task_id = std::to_string(i);
tasks.emplace_back(task_id);
}
当您在向量中放置新任务时,向量的存储空间必须增长。当它增长时,其所有现有内容将被移至新分配的存储中。这是一个问题,因为您已经在驻留在旧存储位置的对象上执行线程。
最简单的解决方法是在创建任务之前在向量中
reserve
提供足够的存储空间。这将确保 emplace_back
不会使向量增长超过其初始容量,因此对象不会被移动:
std::vector<MyTaskM> tasks;
tasks.reserve(3);
for (int i = 1; i <= 3; ++i) {
auto task_id = std::to_string(i);
tasks.emplace_back(task_id);
}
这仍然不是很漂亮。事实上,您需要使
TaskM
可移动,移动时会导致未定义的行为,这是一个坏主意。您使用 std::unique_ptr
的解决方案比这更好。显式删除移动构造函数甚至可能是一个想法,以明确此类永远不应该被移动。
考虑将任务与线程逻辑解耦,也许可以阅读有关线程池的内容。线程池本质上是处于待机状态的线程的集合,准备好执行分配给它们的任务。这是一种更灵活且可扩展的并行运行任务的方式。