c++multithreadingc++20barrier

shared_ptr of barrier doesn't keep the expected counter


I have 2 code examples syncing 2 threads with std::barrier{2}. In one I create the barrier statically, and in one I create it dynamically. The 2nd way mimics the way I want to use the barrier - since number of threads - and hence the "size" of the barrier, is something I'll know only on runtime, making it impossible to declare it static.

My question is - why the static snippet works, where the dynamic snippet (using shared pointers) doesn't (it just hangs...)

My snippets are compiled and run with: clang++-15 -l pthread --std=c++20 demo.cpp && ./a.out (I've also used g++-11)

Updated (3rd snippet working! :) )

#include <barrier>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <vector>

// Working
// static std::barrier b{2};

// int task() {
// b.arrive_and_wait();
// std::cout << "start\n";
// b.arrive_and_wait();
// std::cout << "stop\n";
// return 0;
//}

// int main() {
// std::vector<std::future<int>> promises;
// for (int i = 0; i < 2; ++i) {
// auto promise = std::async(task);
// promises.push_back(std::move(promise));  // Why must I use std::move?
//}
// for (auto& promise : promises) {
// std::cout << promise.get() << std::endl;
//}
// return 0;
//}

// Not working (1) - reference
// int task(std::barrier<std::function<void()>>& b) {
// std::cout << "start\n";
// b.arrive_and_wait();
// std::cout << "stop\n";
// b.arrive_and_wait();
// return 0;
//}

// int main() {
// std::vector<std::future<int>> promises;
// std::barrier<std::function<void()>> barrier{2};

// for (int i = 0; i < 2; ++i) {
// auto promise = std::async(task, std::ref(barrier));
// promises.push_back(std::move(promise));  // Why must I use std::move?
//}
// for (auto& promise : promises) {
// std::cout << promise.get() << std::endl;
//}
// return 0;
//}

// Working!!
int task(std::shared_ptr<std::barrier<>> b) {
  std::cout << "start\n";
  b->arrive_and_wait();
  std::cout << "stop\n";
  b->arrive_and_wait();
  return 0;
}

int main() {
  std::vector<std::future<int>> promises;
  auto barrier = std::make_shared<std::barrier<>>(2);

  for (int i = 0; i < 2; ++i) {
    auto promise = std::async(task, barrier);
    promises.push_back(std::move(promise));  // Why must I use std::move?
  }
  for (auto& promise : promises) {
    std::cout << promise.get() << std::endl;
  }
  return 0;
}

Solution

  • You're attempting to call a default-initialized std::function.

    Your two examples use different CompletionFunction types.

    Since you never initialize your std::barrier's CompletionFunction and std::barrier's CompletionFunction isn't allowed to throw exceptions, you get undefined behavior.


    (Technically I think it's undefined behavior to use std::function as the CompletionFunction type at all, since std::is_nothrow_invocable_v<std::function<void()>&> is false)