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

Sam*_*eau 6 c++ multithreading barrier

您如何将 an 存储std::barrier为班级成员,

因为完成函数可以是 lambda,所以您无法正确键入它,并且使用 anstd::function< void(void) noexcept >也不起作用,因为std::function似乎不支持该noexcept关键字。

std::barrier所以看来完成函数没有通用的基本类型

小例子

#include <barrier>

struct Storage
{
    std::barrier< ?? > barrier;
}
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 1

警告:此答案包含错误

即使它缺乏自适应构造函数,您也可以CompletionFunction进行类型擦除。std::barrier它可能需要额外的铸造。

由于std::barrier对它的各种要求,您可能必须使用仅移动类型的擦除功能CompletionFunction

请注意,std::barrier不支持分配 - 使用智能存储(唯一的 ptr 似乎最好)可能会解决您在这里遇到的任何问题。无论您使用哪种结构,包装结构的语义都会变得复杂。请注意,复制屏障在语义上是无意义的;移动障碍是有意义的。

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

std::barrier不支持多态CompletionFunction类型/值——它没有在其他类型中找到的自适应构造函数。因此,无论其支持如何,使用std::functionin There 都是行不通的——两个std::barrier不同CompletionFunction类型的 s 是不相关的,并且不能相互分配。

您可以自己输入擦除它,然后编写一个poly_barrier. 这样做的障碍(双关语)是似乎arrival_token不支持多态性;可能无法保证在不同std::barrier<X>情况下它是同一类型。

然而,它是 MoveConstructible、MoveAssignable 和 Destructible,因此我们也可以键入擦除它。

第一个 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;
};
Run Code Online (Sandbox Code Playgroud)

我们现在写一个虚函数表:

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;
  }
};
Run Code Online (Sandbox Code Playgroud)

然后我们使用:

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;
};
Run Code Online (Sandbox Code Playgroud)

鲍勃是你叔叔。

上面可能有错别字。它也不支持将任意障碍移入其中;添加演员应该会让这变得容易。

所有涉及arrive内存分配的调用,以及wait由于不知道 anarrival_token是什么而导致的所有释放调用;理论上,我们可以创建一个大小有限的堆栈类型擦除令牌,如果我自己使用它,我可能会这样做。

arrive_and_wait并且arrive_and_drop不使用堆分配。如果你不需要它们,你可以放弃arrive它们。wait

一切都通过手动 vtable 进行反弹,因此会对性能造成一些影响。你必须检查它们是否足够好。

我知道这种技术是 C++ 值样式类型擦除,我们以 C 风格手动实现多态性,但我们使用 C++ 模板自动化类型擦除生成代码。当我们在语言中进行编译时反射(敲木头)时,它会变得不那么丑陋,并且是您可以做的事情来实现std::function

如果您将到达令牌从一个障碍传递到另一个障碍,则代码会以有趣的方式爆炸。但确实如此std::barrier,所以这似乎很公平。


一种完全不同的方法,具有有趣的相似实现,是编写一个 nothrow-on-call std::function

高效实现std::function通常涉及与上述方法类似的方法vtable,以及小型缓冲区优化(SBO),以避免使用小型函数对象进行内存分配(我提到要使用小型函数对象进行内存分配poly_arrival_token)。