Given the std::tuple
,
using Tuple1 = std::tuple<Foo1*, Bar1*, std::shared_ptr<std::mutex>>;
using Tuple2 = std::tuple<Foo2*, Bar2*, std::shared_ptr<std::mutex>>;
std::tuple<Tuple1, Tuple2> tuple;
And the function,
void baz()
{
auto tup = std::get<0>(tuple);
std::lock_guard<std::mutex> lk(*std::get<2>(tup));
// Do something with std::get<0>(tup) and std::get<1>(tup)
}
According to this question on SO accessing a std::tuple
is not inherently thread-safe, but what about in the case of the example code? Is it possible for undefined/strange things to happen?
This is assuming FooN
& BarN
are only ever accessed after the lock.
Quoting from the perfect answer to the question you linked:
However, if the parameter were const, then get would not be considered to provoke a data race with other const calls to get.
This is basically your answer. Make each and every get
call (on any tuple that's not completely protected by a mutex) on a const
tuple and you're safe.
This means your code as posted is not safe. Modify like so:
void baz()
{
// vvvv just being explicit here
auto const & tup = std::get<0>(static_cast<decltype(tuple) const &>(tuple));
std::lock_guard<std::mutex> lk(*std::get<2>(tup));
// Dereference std::get<0>(tup) and std::get<1>(tup),
// use the pointed to objects at will, nothing else
// Not ok, because it could interfer with the call in initialisation of tup of another thread
// auto non_const_tup = std::get<0>(tuple)
}
Currently the only solution I see is using a tuple like:
std::tuple<
std::shared_pointer<std::mutex>,
std::unique_pointer<std::tuple<Foo1*, Bar1*>>
// Mutex and pointer to tuple for Foo2 and Bar2
>
The required const
will stick to everything (except pointer targets).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With