C++ std::barrier作为类成员

u91tlkcl  于 9个月前  发布在  其他
关注(0)|答案(2)|浏览(72)

如何将std::barrier存储为类成员,
因为完成函数可以是lambda,所以你不能正确地输入它,并且使用std::function< void(void) noexcept >也不起作用,因为std::function似乎不支持noexcept关键字。
因此,std::barrier完成函数似乎没有泛型基类型
小例子

#include <barrier>

struct Storage
{
    std::barrier< ?? > barrier;
}

字符串

enyaitl3

enyaitl31#

警告:此答案包含错误

你可以把一个类型擦除的CompletionFunction放到std::barrier中,即使它没有自适应构造函数。它可能需要额外的转换。
由于std::barrier对其CompletionFunction的各种要求,您可能必须使用仅移动类型的擦除函数。
注意std::barrier不支持赋值-使用smart-storage(unique ptr似乎是最好的)可能会解决你在这里遇到的任何问题。无论你使用哪种 Package 结构, Package 结构的语义都会变得复杂。注意复制一个屏障在语义上是无意义的;移动一个屏障可能是有意义的。

原始的,过于复杂的答案:

std::barrier不支持多态的CompletionFunction类型/值--它没有其他类型中的适配构造函数。因此,在中使用std::function是不可行的,不管它支持什么--两个具有不同CompletionFunction类型的std::barrier是不相关的,不能相互赋值。
你可以自己输入erase,然后写一个poly_barrier。这样做的障碍(双关语)是arrival_token似乎不支持多态性;不能保证在不同的std::barrier<X>情况下它是相同的类型。
它是MoveConstructible,MoveAssignable和Destructible,所以我们也可以输入erase it
第一个API草图:

struct poly_barrier;
struct poly_barrier_vtable;
struct poly_arrival_token:private std::unique_ptr<void> {
  friend struct poly_barrier_vtable;
  poly_arrival_token(poly_arrival_token&&)=default;
  private:
    explicit poly_arrival_token(std::unique_ptr<void> ptr):
      poly_arrival_token(std::move(ptr))
    {}
};
struct poly_barrier_vtable;

struct poly_barrier {
  template<class CF>
  poly_barrier( std::ptrdiff_t expected, CF cf );
  ~poly_barrier();
  poly_barrier& operator=(poly_barrier const&)=delete;
  poly_arrival_token arrive( std::ptrdiff_t n = 1 );
  void wait( poly_arrival_token&& ) const;
  void arrive_and_wait();
  void arrive_and_drop();
private:
  poly_barrier_vtable const* vtable = nullptr;
  std::unique_ptr<void> state;
};

字符串
我们现在写一个vtable:

struct poly_barrier_vtable {
  void(*dtor)(void*) = 0;
  poly_arrival_token(*arrive)(void*, std::ptrdiff_t) = 0;
  void(*wait)(void const*, poly_arrival_token&& ) = 0;
  void(*arrive_and_wait)(void*) = 0;
  void(*arrive_and_drop)(void*) = 0;
private:
  template<class CF>
  static poly_arrival_token make_token( std::barrier<CF>::arrival_token token ) {
    return poly_arrival_token(std::make_unique<decltype(token)>(std::move(token)));
  }
  template<class CF>
  static std::barrier<CF>::arrival_token extract_token( poly_arrival_token token ) {
    return std::move(*static_cast<std::barrier<CF>::arrival_token*>(token.get()));
  }
protected:
  template<class CF>
  poly_barrier_vtable create() {
    using barrier = std::barrier<CF>;
    return {
      +[](void* pb){
         return static_cast<barrier*>(pb)->~barrier();
      },
      +[](void* pb, std::ptrdiff_t n)->poly_arrival_token{
        return make_token<CF>(static_cast<barrier*>(pb)->arrive(n));
      },
      +[](void const* bp, poly_arrival_token&& token)->void{
        return static_cast<barrier const*>(pb)->wait( extract_token<CF>(std::move(token)) );
      },
      +[](void* pb)->void{
        return static_cast<barrier*>(pb)->arrive_and_wait();
      },
      +[](void* pb)->void{
        return static_cast<barrier*>(pb)->arrive_and_drop();
      }
    };
  }
public:
  template<class CF>
  poly_barrier_vtable const* get() {
    static auto const table = create<CF>();
    return &table;
  }
};


然后我们用途:

struct poly_barrier {
  template<class CF>
  poly_barrier( std::ptrdiff_t expected, CF cf ):
    vtable(poly_barrier_vtable<CF>::get()),
    state(std::make_unique<std::barrier<CF>>( expected, std::move(cf) ))
  {}
  ~poly_barrier() {
    if (vtable) vtable->dtor(state.get());
  }
  poly_barrier& operator=(poly_barrier const&)=delete;
  poly_arrival_token arrive( std::ptrdiff_t n = 1 ) {
    return vtable->arrive( state.get(), n );
  }
  void wait( poly_arrival_token&& token ) const {
    return vtable->wait( state.get(), std::move(token) );
  }
  void arrive_and_wait() {
    return vtable->arrive_and_wait(state.get());
  }
  void arrive_and_drop() {
    return vtable->arrive_and_drop(state.get());
  }
private:
  poly_barrier_vtable const* vtable = nullptr;
  std::unique_ptr<void> state;
};


鲍勃是你叔叔
上面可能有错别字。它也不支持移动任意的障碍;添加一个ctor应该会很容易。
所有对arrive的调用都涉及内存分配,所有对wait的调用都涉及内存释放,因为不知道arrival_token是什么;理论上,我们可以创建一个大小有限的堆栈类型擦除令牌,如果我自己使用这个令牌,我可能会这样做。
arrive_and_waitarrive_and_drop不使用堆分配。如果不需要,可以删除arrivewait
所有的东西都是通过手动vtable跳转的,所以会有一些性能问题,你必须检查它们是否足够好。
我知道这种技术是C++ value-style type erasure,我们用C风格手动实现多态性,但是我们使用C++模板自动生成类型擦除代码。当我们在语言中有编译时反射时,它会变得不那么丑陋(敲木头),并且是你可能会做的事情来实现std::function
如果你将到达令牌从一个障碍传递到另一个障碍,代码会以有趣的方式爆炸,但std::barrier也是如此,所以这似乎是公平的。
一种完全不同的方法,具有有趣的相似实现,是编写一个nothrow-on-call std::function
有效地实现std::function通常涉及到类似于上面的vtable方法的东西,以及小缓冲区优化(SBO),以避免使用小函数对象分配内存(我提到希望使用poly_arrival_token)。

00jrzges

00jrzges2#

下面是一个C++17中的工作示例,使用std::barrier对象作为类成员变量,沿着arrive_and_wait方法同步多个线程:

#include <barrier>
#include <functional>
#include <thread>
#include <vector>

using namespace std;

class Foo{
public:
    explicit Foo(int thCnt) : b(thCnt,[](){}){}
    barrier<void(*)()> b;
    //barrier<function<void()>> b;  //This works also

    void thFun(int thNum){
        printf("thNum: %i\n",thNum);
        b.arrive_and_wait();
    }
};

int main(){
    int numWorkers = 5;
    Foo f(numWorkers + 1); // +1 for the b.arrive_and_wait called in main()
    vector<thread> threadVec(numWorkers);
    for(int i = 0 ; i<numWorkers; ++i){
        threadVec.emplace_back(&Foo::thFun,&f,i);
    }
    f.b.arrive_and_wait(); //barrier waits for all threads to finish.
    for(auto &th : threadVec){
        if(th.joinable()) th.join();
    }
    printf("done\n");
    return 0;
}

字符串

相关问题