5

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.

Drise
  • 4,310
  • 5
  • 41
  • 66
  • std::get does not modify the state of the `tuple`, I can not give you a guarantee but I'm pretty sure it will work as if you accessed the underlying object directly, which means that if the underlying object is thread safe, the operation will be (practically) thread safe. – Gizmo Jun 13 '17 at 16:21

1 Answers1

3

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).

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • If I understand correctly, the pointers in`tup` can't be modified and in order to modify them I would have to reacquire their tuple after the lock? E.g. `auto tupMutable = std::get<0>(tuple)`. – Babar Shariff Jun 13 '17 at 16:45
  • No. You still could have concurrent access to `tuple`. I'll add a solution. – Daniel Jour Jun 13 '17 at 16:48
  • The solution I had in mind turned out to be incorrect. I guess you don't want pointers to the `tupleN` members? – Daniel Jour Jun 13 '17 at 16:56
  • Basically, the function body should be able modify the pointer values of child tuples. – Babar Shariff Jun 13 '17 at 17:02