3
\$\begingroup\$

I am interacting with a C library, whose documentation states that certain functions are not thread safe. Since its functions deal heterogeneously with multiple types, I have written a template wrapper to use it in a homogeneous manner from C++. However, I now need to ensure serialized access to certain functions, serialized across all instantiated template wrappers.

I believe the following approach should work; what I would like to know is, are there any caveats to the following approach, and if so, what are they and how can I get around them?

struct LibMutexPolicy
{
    static std::mutex & getMutexInstance()
    {
        static std::mutex inst;
        return inst;
    }
};

template <typename T>
struct A : private LibMutexPolicy
{
    void foo()
    {
        std::lock_guard<std::mutex> lock(getMutexInstance());
        // Call non thread-safe library code
    }
};
\$\endgroup\$
4
  • 1
    \$\begingroup\$ I would do that. If you use it in a destructor then you need to be careful (just put a call in the constructor to compensate). The reason is if a user of your code creates a static storage duration object (global) then there could be some lifetime issues. \$\endgroup\$ Commented Dec 7, 2014 at 22:03
  • \$\begingroup\$ This will work (keep in mind Loki's comment). Another issue (not sure if this is a problem for your application) is that you will add shared state between the objects, in a non-obvious way. This could be made obvious, by using dependency injection for your mutex. Have you considered it? \$\endgroup\$
    – utnapistim
    Commented Dec 9, 2014 at 10:29
  • \$\begingroup\$ @utnapistim I haven't really considered it; the wrapper is around FFTW, and I've tried quite hard to keep the constructor interface as simple as possible because there are quite a few facets to its functionality. My feeling is that dependency injection would complicate the interface unless it could be done via a true policy-based design (e.g., perhaps the policy has lock() and unlock() and then I use std::lock_guard<LibLockingPolicy>). Any thoughts? Would this make for a better design? \$\endgroup\$
    – wakjah
    Commented Dec 9, 2014 at 16:25
  • \$\begingroup\$ @wakjah, I have added an example in response, using an external (non-intrusive) decorator for synchronization. It is the most flexible I can think of, but it imposes lambdas in client code. \$\endgroup\$
    – utnapistim
    Commented Dec 9, 2014 at 22:49

1 Answer 1

4
\$\begingroup\$

I believe the following approach should work; what I would like to know is, are there any caveats to the following approach, and if so, what are they and how can I get around them?

Consider a decorator implementation, with a visitor function for method access:

template<typename V>
class threadsafe_reference // decorator
{
public:
    using reference_type = V&;
    threadsafe_reference(reference_type v, std::mutex& m): value_(v), mutex_(m) {}

    // synchronized visitor access
    void sync( std::function<void(value_type&)> functor )
    {
        std::lock_guard<std::mutex> lock{ mutex_ };
        functor( value_ );
    }

    // you may wish to provide unsynchronized reference access here
private:
    reference_type value_;
    std::mutex& mutex_; 
};

template<typename V>
threadsafe_reference<V> make_threadsafe(V& value, std::mutex& m)
{
    return threadsafe_reference<V>{ value, m };
} 

Client code:

struct A // agnostic to threading
{
    void do_stuff_n_things( int i ) {};
};

A a; // need to pass this on two threads
mutex m;

auto tsa = make_threadsafe(a, m); // proxy accessor

Thread access:

 tsa.sync( []( A& self ) { self.do_things_n_stuff(10); } );

The code is incomplete (you'd need a specialization of sync for const access and so on) but it should be enough to give you a picture.

Advantages:

  • this is a non-intrusive decorator that you can use over virtually any type

  • separates concerns nicely and allows you to implement and test them separately (e.g. test A, then test the synchronization code)

Minor disadvantage:

  • the access to A's methods requires lambdas/external functions
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.