这个RAII独占资源结账对象/经理组合线程是否安全?

时间:2015-08-20 17:15:00

标签: c++ multithreading templates c++11 thread-safety

我想编写一个包装器,允许专门检出资源,并在持有者超出范围后自动返回资源。

(多线程)客户端代码中的用法如下:

{
  auto myResource(_manager.checkoutExclusive());

  doStuffWithoutEverStoringTheValue(myResource.val());
} //resource is returned to _manager

我知道将IAlgorithm交给_manager以便在资源上执行会更干净,但目前这对我来说不是最佳解决方案。

我还想提供一个并行检出选项,允许多个并发检出(例如,对于只读访问,或者如果客户端处理资源本身的线程安全性)。当然,平行和独家结账不能混在一起。

以下是我尝试解决此问题。除了我无法避免的重复代码(const /非const版本的checkout),或者为了清晰起见不想避免(ExclusiveCheckout / ParallelCheckout),我的主要问题是:

这个线程安全吗? (我很确定我错过了一个微妙的竞争条件。)

#include <mutex>
#include "global/ScopeGuard11.h" //Using the folly::ScopeGuard

//Forward declarations for friend statements.
template<typename T>
class ExclusiveCheckout;

template<typename T>
class ParallelCheckout;

template<typename T, typename TConst = T const>
class CheckoutValue;

// Interface for returning an exclusively checked out resource
class IReturnExclusive
{
  template<typename T>
  friend class ExclusiveCheckout;

  protected:

    virtual void returnExclusive() const = 0;

};

// Holder for the exclusively checked out resource. Only move construction and val() are public.
// Destruction returns the resource.
template<typename T>
class ExclusiveCheckout
{
  template<typename T, typename TConst>
  friend class CheckoutValue;

  public:

    ExclusiveCheckout(ExclusiveCheckout<T> &&src)
    : _returnTo(src._returnTo), _value(src._value)
    {
      src._returnTo = nullptr;
      src._value = nullptr;
    }

    ~ExclusiveCheckout()
    { 
      if (_returnTo)
      {
        _returnTo->returnExclusive();
      }
    }

    virtual T &val() final
    { 
      return *_value;
    }


  private:

    IReturnExclusive const *_returnTo;
    T *_value;

    ExclusiveCheckout(IReturnExclusive const &returnTo, T &value)
    : _returnTo(&returnTo), _value(&value)
    { }

    ExclusiveCheckout(ExclusiveCheckout<T> const &src); //disallowed
    ExclusiveCheckout const &operator=(ExclusiveCheckout<T> const &other); //disallowed
};


// Interface for returning a parallely checked out resource
class IReturnParallel
{
  template<typename T>
  friend class ParallelCheckout;

  protected:

    virtual void returnParallel() const = 0;

};

// Holder for the parallely checked out resource. Only move construction and val() are public.
// Destruction returns the resource.
template<typename T>
class ParallelCheckout
{
  template<typename T, typename TConst>
  friend class CheckoutValue;

  public:

    ParallelCheckout(ParallelCheckout<T> &&src)
    : _returnTo(src._returnTo), _value(src._value)
    {
      src._returnTo = nullptr;
      src._value = nullptr;
    }

    ~ParallelCheckout()
    { 
      if (_returnTo)
      {
        _returnTo->returnParallel();
      }
    }

    virtual T &val() final
    {
      return *_value;
    }


  private:

    IReturnParallel const *_returnTo;
    T *_value;

    ParallelCheckout(IReturnParallel const &returnTo, T &value)
    : _returnTo(&returnTo), _value(&value)
    { }

    ParallelCheckout(ParallelCheckout<T> const &src); //disallowed
    ParallelCheckout const &operator=(ParallelCheckout<T> const &other); //disallowed
};


// The resource manager.
template<typename T, typename TConst>
class CheckoutValue final : protected IReturnExclusive,
                            protected IReturnParallel
{

public:

  CheckoutValue()
  : _checkoutValue(), _parallelCnt(0)
  { }

  CheckoutValue(T const &checkoutValue)
  : _checkoutValue(checkoutValue), _parallelCnt(0)
  { }

  virtual ~CheckoutValue() { };

  void setValue(T const &checkoutValue)
  {
    // Only change the resource if noone is using it
    std::lock_guard<std::mutex> guard(_exclusiveMutex);
    _checkoutValue = checkoutValue;
  }


  ExclusiveCheckout<T> checkoutExclusive()
  {
    ScopeGuard guard = folly::makeGuard([&] { _exclusiveMutex.unlock(); });
    _exclusiveMutex.lock();

    ExclusiveCheckout<T> ret(*this, _checkoutValue);

    guard.dismiss();

    return ret;
  }


  ExclusiveCheckout<TConst> checkoutExclusive() const
  {
    ScopeGuard guard = folly::makeGuard([&] { _exclusiveMutex.unlock(); });
    _exclusiveMutex.lock();

    ExclusiveCheckout<TConst> ret(*this, _checkoutValue);

    guard.dismiss();

    return ret;
  }


  ParallelCheckout<T> checkoutParallel()
  {
    std::lock_guard<std::mutex> guardParallel(_parallelMutex);

    ScopeGuard guard = folly::makeGuard([&] { _exclusiveMutex.unlock(); });
    if (_parallelCnt == 0)
    {
      _exclusiveMutex.lock();
    }
    else
    {
      guard.dismiss();
    }

    ParallelCheckout<T> ret(*this, _checkoutValue);

    ++_parallelCnt;

    guard.dismiss();

    return ret;
  }


  ParallelCheckout<TConst> checkoutParallel() const
  {
    std::lock_guard<std::mutex> guardParallel(_parallelMutex);

    ScopeGuard guard = folly::makeGuard([&] { _exclusiveMutex.unlock(); });
    if (_parallelCnt == 0)
    {
      _exclusiveMutex.lock();
    }
    else
    {
      guard.dismiss();
    }

    ParallelCheckout<TConst> ret(*this, _checkoutValue);

    ++_parallelCnt;

    guard.dismiss();

    return ret;
  }


protected:

  virtual void returnExclusive() const final override
  {
    _exclusiveMutex.unlock();
  }

  virtual void returnParallel() const final override
  {  
    std::lock_guard<std::mutex> guardParallel(_parallelMutex);

    --_parallelCnt;

    if (_parallelCnt == 0)
    {
      _exclusiveMutex.unlock();
    }
  }


private:

  mutable std::mutex _exclusiveMutex;
  mutable std::mutex _parallelMutex;
  mutable int _parallelCnt;

  T _checkoutValue;

};

1 个答案:

答案 0 :(得分:2)

在C ++ 14中,这非常简短。并且很容易确认它是正确的。

实现你需要的C ++ 14,而不是手动编写,可能是最好的。

// holds a "reference" to a T and a Lock
// can be moved-from, in which case it holds nothing:
template<class T, class Lock>
struct locked_resource:Lock {
  locked_resource( T& t, Lock&& lock ):
    Lock(std::move(lock)),
    data(std::addressof(t))
  {}

  locked_resource(locked_resource&&o):Lock(std::move(o)),data(o.data){
    o.data=nullptr;
  }

  // use pointer-semantics for "is valid" and "get" via operator*:
  T& operator*() const { return *data; }
  explicit operator bool()const{ return data; }
private:
  T* data;
};

// shared and exclusive lock types
// also used as shared and exclusive checkout tags:
using shared = std::shared_lock<std::shared_timed_mutex>;
using exclusive = std::unique_lock<std::shared_timed_mutex>;
template<class T>
struct protected_resource {
  // access methods:
  template<class Access>
  locked_resource<T, Access> checkout(){
    return access<T,Access>(*this);
  }
  template<class Access>
  locked_resource<T const, Access> checkout() const {
    return access<T const,Access>(*this);
  }

  // ctors:
  protected_resource()=default;
  template<class U>
  explicit protected_resource(U&& u):t(std::forward<U>(u)) {}

  // setter.  I perfect forward:
  template<class U>
  void set(U&& u) {
    auto x = checkout<exclusive>();
    *x = std::forward<U>(u);
  }
  // for .set({blah}) support:
  void set(T&& t) {
    set<T>(std::move(t));
  }
private:
  // hey look, a one-liner!  This, plus 2 other one-liners
  // replaces 37 lines of 4 functions source in OP: (not counting whitespace)
  template<class U, class Lock, class Self>
  friend locked_resource<U, Lock> access(Self& self) {
    return {t, Lock(m)};
  }
  std::shared_timed_mutex m; // or shared_mutex
  T t;
};

从资源管理中删除所有混乱的共享/独占代码。

现在只需实施shared_timed_mutexshared_lock即可。因为它们不再与您的业务逻辑纠缠在一起,所以您将有更轻松的时间。

例如,

You could steal an implementation from Howard或来自boost

使用示例:

protected_resource<int> _manager;
{
  auto myResource = _manager.checkout<exclusive>();

  doStuffWithoutEverStoringTheValue(*myResource);
} //resource is returned to _manager