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)
即使它缺乏自适应构造函数,您也可以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)。