std::线程移动问题(使用成员函数)

问题描述 投票:0回答:1
  1. MyTask 类用一些任务数据(例如“id”)封装线程。
  2. MyTaskM 类是可移动的,因为想要将它们组织在容器中。
  3. 当线程访问成员数据时,发生Segmentation failure。不知道为什么无法访问会员数据。
  • 规格:
    • g++.exe(Rev2,由 MSYS2 项目构建)13.2.0
    • c++ 标准 20

只是想知道如何以及为什么?泰:)

#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;
}
c++ multithreading concurrency segmentation-fault threadpool
1个回答
1
投票

您的线程绑定到创建它们的原始对象。如果移动该对象,线程仍绑定到旧对象。不仅如此,您还可以在没有同步的情况下移动。这两件事都是不好的。您需要修改您的设计。

主要问题在这里:

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
的解决方案比这更好。显式删除移动构造函数甚至可能是一个想法,以明确此类永远不应该被移动。

考虑将任务与线程逻辑解耦,也许可以阅读有关线程池的内容。线程池本质上是处于待机状态的线程的集合,准备好执行分配给它们的任务。这是一种更灵活且可扩展的并行运行任务的方式。

© www.soinside.com 2019 - 2024. All rights reserved.