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

u91tlkcl  于 2024-01-09  发布在  其他
关注(0)|答案(2)|浏览(111)

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

  1. #include <barrier>
  2. struct Storage
  3. {
  4. std::barrier< ?? > barrier;
  5. }

字符串

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草图:

  1. struct poly_barrier;
  2. struct poly_barrier_vtable;
  3. struct poly_arrival_token:private std::unique_ptr<void> {
  4. friend struct poly_barrier_vtable;
  5. poly_arrival_token(poly_arrival_token&&)=default;
  6. private:
  7. explicit poly_arrival_token(std::unique_ptr<void> ptr):
  8. poly_arrival_token(std::move(ptr))
  9. {}
  10. };
  11. struct poly_barrier_vtable;
  12. struct poly_barrier {
  13. template<class CF>
  14. poly_barrier( std::ptrdiff_t expected, CF cf );
  15. ~poly_barrier();
  16. poly_barrier& operator=(poly_barrier const&)=delete;
  17. poly_arrival_token arrive( std::ptrdiff_t n = 1 );
  18. void wait( poly_arrival_token&& ) const;
  19. void arrive_and_wait();
  20. void arrive_and_drop();
  21. private:
  22. poly_barrier_vtable const* vtable = nullptr;
  23. std::unique_ptr<void> state;
  24. };

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

  1. struct poly_barrier_vtable {
  2. void(*dtor)(void*) = 0;
  3. poly_arrival_token(*arrive)(void*, std::ptrdiff_t) = 0;
  4. void(*wait)(void const*, poly_arrival_token&& ) = 0;
  5. void(*arrive_and_wait)(void*) = 0;
  6. void(*arrive_and_drop)(void*) = 0;
  7. private:
  8. template<class CF>
  9. static poly_arrival_token make_token( std::barrier<CF>::arrival_token token ) {
  10. return poly_arrival_token(std::make_unique<decltype(token)>(std::move(token)));
  11. }
  12. template<class CF>
  13. static std::barrier<CF>::arrival_token extract_token( poly_arrival_token token ) {
  14. return std::move(*static_cast<std::barrier<CF>::arrival_token*>(token.get()));
  15. }
  16. protected:
  17. template<class CF>
  18. poly_barrier_vtable create() {
  19. using barrier = std::barrier<CF>;
  20. return {
  21. +[](void* pb){
  22. return static_cast<barrier*>(pb)->~barrier();
  23. },
  24. +[](void* pb, std::ptrdiff_t n)->poly_arrival_token{
  25. return make_token<CF>(static_cast<barrier*>(pb)->arrive(n));
  26. },
  27. +[](void const* bp, poly_arrival_token&& token)->void{
  28. return static_cast<barrier const*>(pb)->wait( extract_token<CF>(std::move(token)) );
  29. },
  30. +[](void* pb)->void{
  31. return static_cast<barrier*>(pb)->arrive_and_wait();
  32. },
  33. +[](void* pb)->void{
  34. return static_cast<barrier*>(pb)->arrive_and_drop();
  35. }
  36. };
  37. }
  38. public:
  39. template<class CF>
  40. poly_barrier_vtable const* get() {
  41. static auto const table = create<CF>();
  42. return &table;
  43. }
  44. };


然后我们用途:

  1. struct poly_barrier {
  2. template<class CF>
  3. poly_barrier( std::ptrdiff_t expected, CF cf ):
  4. vtable(poly_barrier_vtable<CF>::get()),
  5. state(std::make_unique<std::barrier<CF>>( expected, std::move(cf) ))
  6. {}
  7. ~poly_barrier() {
  8. if (vtable) vtable->dtor(state.get());
  9. }
  10. poly_barrier& operator=(poly_barrier const&)=delete;
  11. poly_arrival_token arrive( std::ptrdiff_t n = 1 ) {
  12. return vtable->arrive( state.get(), n );
  13. }
  14. void wait( poly_arrival_token&& token ) const {
  15. return vtable->wait( state.get(), std::move(token) );
  16. }
  17. void arrive_and_wait() {
  18. return vtable->arrive_and_wait(state.get());
  19. }
  20. void arrive_and_drop() {
  21. return vtable->arrive_and_drop(state.get());
  22. }
  23. private:
  24. poly_barrier_vtable const* vtable = nullptr;
  25. std::unique_ptr<void> state;
  26. };


鲍勃是你叔叔
上面可能有错别字。它也不支持移动任意的障碍;添加一个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方法同步多个线程:

  1. #include <barrier>
  2. #include <functional>
  3. #include <thread>
  4. #include <vector>
  5. using namespace std;
  6. class Foo{
  7. public:
  8. explicit Foo(int thCnt) : b(thCnt,[](){}){}
  9. barrier<void(*)()> b;
  10. //barrier<function<void()>> b; //This works also
  11. void thFun(int thNum){
  12. printf("thNum: %i\n",thNum);
  13. b.arrive_and_wait();
  14. }
  15. };
  16. int main(){
  17. int numWorkers = 5;
  18. Foo f(numWorkers + 1); // +1 for the b.arrive_and_wait called in main()
  19. vector<thread> threadVec(numWorkers);
  20. for(int i = 0 ; i<numWorkers; ++i){
  21. threadVec.emplace_back(&Foo::thFun,&f,i);
  22. }
  23. f.b.arrive_and_wait(); //barrier waits for all threads to finish.
  24. for(auto &th : threadVec){
  25. if(th.joinable()) th.join();
  26. }
  27. printf("done\n");
  28. return 0;
  29. }

字符串

展开查看全部

相关问题