我正在为一系列优化算法进行抽象。这些算法可以使用锁定机制或原子操作来串行或多线程运行。

对于算法的多线程版本,我有一个关于完美转发的问题。举例来说,我有一些仿函数,因为价格昂贵,所以我不愿意复制。我可以确保函子是静态的,因为对函子的operator()(...)的调用不会更改对象的状态。下面是一个这样的伪函子:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <thread>
#include <vector>

template <class value_t> struct WeightedNorm {
  WeightedNorm() = default;
  WeightedNorm(std::vector<value_t> w) : w{std::move(w)} {}

  template <class Container> value_t operator()(Container &&c) const & {
    std::cout << "lvalue version with w: " << w[0] << ',' << w[1] << '\n';
    value_t result{0};
    std::size_t idx{0};
    auto begin = std::begin(c);
    auto end = std::end(c);
    while (begin != end) {
      result += w[idx++] * *begin * *begin;
      *begin++ /* += 1 */; // <-- we can also modify
    }
    return result; /* well, return std::sqrt(result), to be precise */
  }

  template <class Container> value_t operator()(Container &&c) const && {
    std::cout << "rvalue version with w: " << w[0] << ',' << w[1] << '\n';
    value_t result{0};
    std::size_t idx{0};
    auto begin = std::begin(c);
    auto end = std::end(c);
    while (begin != end) {
      result += w[idx++] * *begin * *begin;
      *begin++ /* += 1 */; // <-- we can also modify
    }
    return result; /* well, return std::sqrt(result), to be precise */
  }

private:
  std::vector<value_t> w;
};


如上所示,该函子可能还具有某些成员函数的参考限定符(尽管在上面,它们彼此没有区别)。此外,允许功能对象修改其输入c。为了将这个函子正确地完善到算法中的工作线程,我想到了以下几点:

template <class value_t> struct algorithm {
  algorithm() = default;
  algorithm(const unsigned int nthreads) : nthreads{nthreads} {}

  template <class InputIt> void initialize(InputIt begin, InputIt end) {
    x = std::vector<value_t>(begin, end);
  }

  template <class Func> void solve_ref_1(Func &&f) {
    std::vector<std::thread> workers(nthreads);
    for (auto &worker : workers)
      worker = std::thread(&algorithm::kernel<decltype((f)), decltype(x)>, this,
                           std::ref(f), x);
    for (auto &worker : workers)
      worker.join();
  }

  template <class Func> void solve_ref_2(Func &&f) {
    auto &xlocal = x;
    std::vector<std::thread> workers(nthreads);
    for (auto &worker : workers)
      worker = std::thread([&, xlocal]() mutable { kernel(f, xlocal); });
    for (auto &worker : workers)
      worker.join();
  }

  template <class Func> void solve_forward_1(Func &&f) {
    std::vector<std::thread> workers(nthreads);
    for (auto &worker : workers)
      worker = std::thread(
          &algorithm::kernel<decltype(std::forward<Func>(f)), decltype(x)>,
          this, std::ref(f), x); /* this is compilation error */
    for (auto &worker : workers)
      worker.join();
  }

  template <class Func> void solve_forward_2(Func &&f) {
    auto &xlocal = x;
    std::vector<std::thread> workers(nthreads);
    for (auto &worker : workers)
      worker = std::thread(
          [&, xlocal]() mutable { kernel(std::forward<Func>(f), xlocal); });
    for (auto &worker : workers)
      worker.join();
  }

private:
  template <class Func, class Container> void kernel(Func &&f, Container &&c) {
    std::forward<Func>(f)(std::forward<Container>(c));
  }

  std::vector<value_t> x;
  unsigned int nthreads{std::thread::hardware_concurrency()};
};


基本上,编写上述内容时,我想到的是algorithm::solve_ref_1algorithm::solve_ref_2仅在使用lambda函数方面彼此不同。最后,他们两个都用kernel的左值引用和f的左值调用x,其中x由于std::thread的工作方式或捕获而在每个线程中复制xlocal副本中的f。它是否正确?我应该谨慎选择一个吗?

到目前为止,我还无法完成我想实现的目标。我没有制作不必要的f副本,但是我也没有遵守其参考限定词。然后,我想到了将kernel转发到algorithm::solve_forward_1。上面,由于删除了用于右值引用的std::ref构造函数,我找不到编译algorithm::solve_forward_2的方法。但是,使用lambda函数方法的f似乎正在工作。 “似乎有效”是指以下主要程序

int main(int argc, char *argv[]) {
  std::vector<double> x{1, 2};
  algorithm<double> alg(2);
  alg.initialize(std::begin(x), std::end(x));

  alg.solve_ref_1(WeightedNorm<double>{{1, 2}});
  alg.solve_ref_2(WeightedNorm<double>{{1, 2}});
  // alg.solve_forward_1(WeightedNorm<double>{{1, 2}});
  alg.solve_forward_2(WeightedNorm<double>{{1, 2}});

  return 0;
}


编译并打印以下内容:

./main.out
lvalue version with w: 1,2
lvalue version with w: 1,2
lvalue version with w: 1,2
lvalue version with w: 1,2
rvalue version with w: 1,2
rvalue version with w: 1,2


简而言之,我有两个主要问题:


有什么理由让我比其他版本更喜欢lambda函数版本,反之亦然?
在我的情况下,是否可以多次完美转发函子w /好吗?


我在上面问2.,因为在the answer中另一个问题,作者说:


  但是,您不能转发多次,因为这没有任何意义。转发意味着您可能会将参数一直移动到最终调用者,并且一旦移动它就消失了,因此您不能再使用它。


我认为,就我而言,我没有采取任何行动,而是试图尊重参考资格。在主程序的输出中,我可以看到1,2在右值版本中具有正确的值,即,但这并不意味着我正在做一些未定义的行为,例如尝试访问已移动的对象。向量的值。

如果您能帮助我更好地理解这一点,我将不胜感激。我也愿意就我试图解决问题的方式提出任何其他反馈。

最佳答案

没有理由更喜欢
for周期内转发不正确。您不能两次转发相同的变量:


template <typename T>void func(T && param){ func1(std::forward<T>(param)); func2(std::forward<T>(param)); // UB}

另一方面,链转发(std::forward(std::forward(…)))也可以。

关于c++ - 在多线程代码中转发,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49239642/

10-12 17:30