davidhigh davidhigh - 10 months ago 108
C++ Question

C++ thread-safe bracket operator proxy

Given a simple wrapper around a standard vector, what is a good way to implement

in a thread-safe way in order to be able to set the content as usual?

struct bracket_operator_proxy;

struct example
auto operator[](size_t i) const { return bracket_operator_proxy(v, m, i); }
std::vector<double> v;
std::mutex m;

Here is my quick and naive attempt for

struct bracket_operator_proxy
bracket_operator_proxy(std::vector<double>& v, std::mutex& m, int i)
: v(v), m(m), i(i) {}

operator double() const
std::lock_guard<std::mutex> l(m);
return v[i];

auto operator=(double d)
std::lock_guard<std::mutex> l(m);
v[i] = d;
return d;

//... further assignment operators ...
std::vector<double>& v;
std::mutex& m;
int i;

Is this already enough? Or am I missing something which will blow my leg off?

Answer Source

Once you have operator-> (which is very useful) you'll need to return a -> proxy which extends lock lifetime until end of statement, and exposes you to single threaded deadlock.

Look at thread safe monads/functor/wrapper like the one here. It doesn't make the locks completely transparent, but they should not be.

  • Do not share data between threads

  • If you share data, make it immutable

  • If it must be mutated, isolate access through a bottleneck of known safe design. A message queue say.

  • If you cannot do that, consider redesign

  • Really. Atomic maybe?

  • Have a limited set of functions that manage locks explicitly

  • Ok, now wrap in reader/writer monad as above, with easy-ish compound operations

  • Make code that magically gets locks and looks just like non-thread interacting code, thus lulling your readers into a false sense of security and efficiency

In decreasing preference.

The dangerous and hard part of thread safety is not the fact that the syntax is awkward. It is that lock based thread safety is nearly impossible to prove correct and safe. Making the syntax easier to use is not a high value goal.

As an example, v[i]=v[i+1] is fraught with a lack of synchronization: between the read and the write anything could have changed. Let alone the problem of "is i a valid index?"