【问题标题】:std::thread constructor with no parameter没有参数的 std::thread 构造函数
【发布时间】:2016-01-27 12:42:49
【问题描述】:

根据cppreference.comstd::thread没有参数的构造函数表示:

创建不代表线程的新线程对象。

我的问题是:

  1. 为什么我们需要这个构造函数?如果我们使用这个构造函数创建一个thread,我们以后如何“分配”一个线程函数?
  2. 为什么我们没有一个“run(function_address)”方法,这样当不带参数构造时,我们可以为 thread 指定一个函数来“运行”。
  3. 或者,我们可以构造一个带有可调用参数(函数、函子等)的thread,但稍后调用“run()”方法来实际执行线程。为什么std::thread不是这样设计的?

【问题讨论】:

  • 线程是可移动的,因此可以交换。

标签: c++ multithreading c++11 stdthread


【解决方案1】:

您的问题表明可能存在一些混淆,将 执行线程 的概念与 std::thread 类型清楚地分开,并将两者与“线程函数”。

  • 执行线程表示通过您的程序的控制流,可能对应于内核管理的操作系统线程。
  • std::thread 类型的对象可以与一个执行线程相关联,或者它可以是“空的”并且不引用任何执行线程
  • 标准 C++ 中没有“线程函数”这样的概念。通过将任何函数传递给std::thread 对象的构造函数,任何函数都可以在新的执行线程中运行。
  1. 为什么我们需要这个构造函数?

构造不涉及执行线程的空状态。您可能希望拥有一个类的成员变量 std::thread,但不希望立即将其与执行线程相关联。。所以你默认构造它,然后启动一个新的执行线程,并将它与std::thread 成员变量相关联。或者你可能想这样做:

std::thread t;
if (some_condition) {
  t = std::thread{ func1, arg1 };
}
else {
  auto result = some_calculation();
  t = std::thread{ func2, arg2, result };
}

默认构造函数允许创建对象t,而无需启动新的执行线程直到需要。

如果我们使用这个构造函数创建一个线程,我们以后如何“分配”一个线程函数呢?

您使用“分配”来“分配”:-)

但是您没有为其分配“线程函数”,这不是std::thread 的用途。你为它分配另一个std::thread

std::thread t;
std::thread t2{ func, args };
t = std::move(t2);

考虑创建一个新的执行线程,而不是“分配一个线程函数”给某物。您不只是分配一个函数,这就是 std::function 的用途。您正在请求运行时创建一个新的执行线程,该线程将由std::thread 对象管理。

  1. 为什么我们没有一个“run(function_address)”方法,这样当不带参数构造时,我们可以指定一个函数来为那个线程“运行”。

因为你不需要它。您可以通过构造带有参数的std::thread 对象来启动新的执行线程。如果您希望该执行线程与现有对象相关联,则可以通过移动分配或交换来实现。

  1. 或者,我们可以构造一个带有可调用参数(函数、函子等)的线程,但稍后调用“run()”方法来实际执行该线程。为什么 std::thread 不是这样设计的?

为什么要这样设计?

std::thread 类型用于管理一个执行线程,该线程不持有供以后使用的可调用对象。如果您想创建一个可调用对象,以后可以在新的执行线程上运行,在 C++ 中有很多方法可以做到这一点(使用 lambda 表达式,或std::bind,或@987654336 @,或std::packaged_task,或自定义函子类型)。 std::thread 的工作是管理一个执行线程,在你想调用它之前不要持有一个可调用对象。

【讨论】:

    【解决方案2】:

    提供了默认构造函数,以便可以创建“空”thread 对象。在构造所述对象时,并非所有thread 对象都将与执行线程相关联。考虑thread 是某个类型的成员并且该类型具有默认构造函数的情况。考虑另一种情况,thread 类型没有“挂起”线程的概念,即它不能在挂起状态下创建。

    thread 类型没有某种“运行”方法,因为最初的设计决策 (IIRC) 之一是在 thread 对象和执行线程之间建立“强”关联.允许threads 被“移动”使这个意图更清晰(在我看来)。因此,将thread 对象的实例移动到“空”对象比尝试“运行”thread 更清晰。

    可以想象,您可以创建一个提供“运行”方法的某种包装类,但我认为这可能是一个更窄的用例,并且可以通过@987654330 的 API 解决 @类

    【讨论】:

    • 谢谢@Niall,但为什么最初的设计偏爱“强”关联?它有什么好处?
    • 当时的很多讨论都是围绕在对象超出范围后如何处理“未连接”线程?鉴于对做什么没有真正的共识,因此调用std::terminate 的结果是因为它提供了“最安全”的结果。 IE。如果程序员打算将其从正在运行的线程中分离出来,那么他们需要对此进行明确说明,并且析构函数中的“连接”可能会导致意外死锁。
    【解决方案3】:

    默认构造函数使您可以创建线程数组:

    thread my_threads[4];
    
    for (int i=0; i<4; i++)
    {
       thread temp(func,...);
       my_threads[i]=move(temp);
    }
    

    使用默认构造函数创建的线程“成为”具有移动构造函数的“真实”线程。

    如果您需要/喜欢,您可以将线程与标准容器一起使用。

    【讨论】:

      【解决方案4】:

      编辑:也许让我们先评论最后一部分:

      3.2 为什么std::thread不是这样设计的?

      我不知道究竟是为什么(肯定有优点和缺点 - 请参阅 Jonathan Wakely 的回答以获取有关其背后理性的更多详细信息),但似乎 c++11 std::thread 的建模更接近 pthreads比例如QT 或 Java 的 QThread/Thread 类,这可能是您困惑的根源。

      关于你的其他问题:

      1.1 为什么我们需要这个构造函数?

      您可能想创建一个std::thread 变量,但不要直接启动线程(例如,类成员变量或静态数组的元素,如alangab 所示)。它与无需文件名即可创建的std::fstream 没有太大区别。

      1.2 如果我们使用这个构造函数创建一个线程,我们以后如何“分配”一个线程函数?

      例如:

      std::thread myThread;
      
      // some other code
      
      myThread = std::thread(foo()); 
      
      1. 为什么我们没有一个“run(function_address)”方法,这样当不带参数构造时,我们可以指定一个函数来为那个线程“运行”。

      我不知道为什么要这样设计,但我看不出run 方法与上述语法相比有什么好处。

      3.1 或者,我们可以构造一个带有可调用参数(函数、函子等)的线程,但稍后调用“run()”方法来实际执行线程。

      您可以通过创建 lambda 或 std::function 对象来模拟这一点,并在您想要运行该函数时创建线程。

      auto myLambda = [=]{foo(param1, param2);};
      // some other code
      std::thread myThread(myLambda);
      

      如果您想使用您描述的语法,我建议您编写自己的 Thread 包装类(应该只需要几十行代码),它(可选)还可以确保线程是分离的或在破坏包装器时加入,在我看来,这是 std::thread 的主要问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-11-06
        • 1970-01-01
        • 2023-03-13
        • 1970-01-01
        • 1970-01-01
        • 2021-12-30
        相关资源
        最近更新 更多