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;
}
CodePudding user response:
You're attempting to call a default-initialized std::function
.
Your two examples use different CompletionFunction
types.
std::barrier b{2}
uses the defaultCompletionFunction
type, which is an unspecified DefaultConstructible function that does nothing.std::make_shared<std::barrier<std::function<void()>>>
usestd::function<void()>
as itsCompletionFunction
type, which while DefaultConstructible, will throw an exception if you attempt to call it without giving it a callable to wrap.
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
)