【问题标题】:How to get std::thread to do general construction of the arguments passed to its function?如何让 std::thread 对传递给其函数的参数进行一般构造?
【发布时间】:2015-03-23 00:52:30
【问题描述】:

std::thread 的构造函数和类似的接口,如std::async 将它们的参数复制到线程可访问的存储中,就像通过函数一样

template <class T>
typename decay<T>::type decay_copy(T&& v) {
    return std::forward<T>(v);
}

这个函数可以处理移动和复制构造函数,但我想知道是否可以概括这些参数的构造方式,类似于.emplace() 在标准容器接口中概括.insert()

例如,我可能有一个只能从int 构造的可移动类型:

struct foo
{
  foo(int arg){}
  foo(foo&& other){}

  foo() = delete;
  foo(const foo&) = delete;
};

是否可以为这样的类型创建一个helper,通过decay_copy 进行复制构造会产生构造其他类型的效果?

我知道如何创建一个解决方案,将foo 的参数复制到线程可访问的存储中,但我想避免这种情况(假设这些参数通常很大并且线程可访问的存储很少)。

这是我最初的尝试:

#include <thread>
#include <utility>

struct foo
{
  foo(int arg){}
  foo(foo&& other){}

  foo() = delete;
  foo(const foo&) = delete;
};


template<class T, class Arg>
struct helper
{
  operator T () const
  {
    return T{arg_};
  }

  Arg arg_;
};


template<class T, class Arg>
helper<T,Arg> help(Arg&& arg)
{
  return helper<T,Arg>{std::forward<Arg>(arg)};
}


int main()
{
  auto lambda = [](const foo& f){};

  std::thread t{lambda, help<foo>(13)};
  t.join();

  return 0;
}

这个解决方案的问题是foo 的所有构造函数参数都通过helper 对象复制到线程可访问的存储中。我只想在foo 对象上花费存储空间。

假设我不能修改lambda,也不能修改std::thread,当给定helper作为参数时,是否有一些技巧会导致decay_copy函数返回一个新构造的foo

【问题讨论】:

  • 你想让std::thread通过调用构造函数的线程创建一个foo类型的对象,但是在新线程的存储上?
  • @dyp:基本上是的。理想情况下,构造函数参数的存储只存在于父线程的堆栈中,foo 对象的存储在子线程的堆栈中。
  • @JaredHoberock:那么,正如我在回答中所说,为什么不在父级中创建 foo 并将其移动到线程中?
  • @rodrigo:很难解释。我没有那个选项——构造函数取决于执行它的线程 ID。
  • 我认为std::thread的界面不支持这个。在内部,它需要为Bob 设置一些存储并在那里构造对象,以便Bob 可以在其主线程函数中访问它们。但似乎std::thread 仅支持将一个参数复制或移动到Bob 相同(衰减)类型的存储上的一个对象。

标签: c++ multithreading templates parameter-passing


【解决方案1】:

我不确定您所说的“线程可访问存储”是什么意思,因为进程中的所有线程通常共享它们的所有内存。

也许你指的是堆栈,但你可以传递完全构造的foo 对象(在主线程中)。 foo 对象将存在于线程堆栈中:

foo f{13};
std::thread t{lambda, f};

一个奇特的选择是使用std::future

#include <future>
template<class T>
struct helper
{
  operator T ()
  {
    return future.get();
  }
  std::future<T> future;
};
template<class T>
helper<T> help(std::future<T> &&p)
{
  return helper<T>{std::forward<std::future<T>>(p)};
}

然后它可以与std::promise一起使用:

std::promise<foo> p;
//you can set the value before or after the thread
std::thread t{lambda, help(p.get_future())};
//either way, std::future::get() is a synchronization point
p.set_value(13);
t.join();

或者使用std::packaged_task 和创建foo 的labmda。但在这种特殊情况下,lambda 将在线程中执行,因此我猜这与您的原始代码之间没有实际区别。

更新:从您的 cmets 来看,我认为您可以使用带有简单 std::promise&lt;void&gt; 的辅助类进行同步:

template<class T>
struct helper
{
  operator T ()
  {
      foo f{x};
      promise.set_value(); // x is no longer needed
      return f;
  }

  std::promise<void> promise;
  int &x;
};


template<class T>
helper<T> help(std::promise<void> &&p, int &x)
{
  return helper<T>{std::forward<std::promise<void>>(p), x};
}

int main()
{
   auto lambda = [](const foo &&f){  };

   std::promise<void> prom_done;
   std::future<void> done = prom_done.get_future(); 

   int x = 42; 
   std::thread t{lambda, help<foo>(std::move(prom_done), x)}; 

   future.get(); //wait until the `foo` is fully created.

   t.join();
}

【讨论】:

  • “线程可访问存储”是language used in the documentation on cppreference.com。在我的平台上,线程不共享它们的内存。
  • @JaredHoberock:好的,我读过它,但仍然不明白它的意思,除了一种模糊的感觉,它是一种花哨的表达方式,没什么特别的。
  • @dyp:感谢您花时间考虑这个问题。不幸的是,您的回答无法解决我的申请。在我的应用程序中,我不能做任何额外的同步、动态分配,甚至不能修改main 函数! :-) 所以它真的必须以某种方式本地化为helper 模板。 FWIW,我认为 c++14 中没有解决方案。
  • @JaredHoberock:不客气!所以你想要:1)在工作线程中运行构造函数; 2) 不要复制或移动构造函数参数; 3) 不等待工作线程运行完构造函数; 4) 构造函数参数将在主线程中立即销毁。这4个加起来是不可能的!只需删除其中一个条件,就很容易了。
  • 其实构造函数需要在父线程中运行。我想避免将构造函数参数复制或移动到工作人员。在我的解决方案中,构造函数参数被复制到 worker,worker 调用构造函数。
猜你喜欢
  • 2021-12-30
  • 1970-01-01
  • 2021-06-30
  • 1970-01-01
  • 2021-04-24
  • 1970-01-01
  • 2017-03-22
  • 2020-12-09
  • 1970-01-01
相关资源
最近更新 更多