簡體   English   中英

該RAII專用資源檢出對象/管理器組合線程安全嗎?

[英]Is this RAII exclusive resource checkout object / manager combination thread safe?

我想編寫一個包裝器,允許專門簽出資源並在持有人超出范圍后自動返回資源。

(多線程)客戶端代碼中的用法如下:

{
  auto myResource(_manager.checkoutExclusive());

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

我知道將IAlgorithm交給_manager在資源上執行會更干凈,但是就目前而言,這並不是我的最佳解決方案。

我還想提供一個並行檢出選項,該選項允許多個並發檢出(例如,用於只讀訪問,或者客戶端是否為資源本身處理線程安全性)。 當然,並行檢查和排他檢查不能混用。

以下是我試圖解決此問題的嘗試。 除了我無法避免的重復代碼(簽出的const /非const版本),或者為了清楚起見,我不想避免的重復代碼(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;

};

在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 由於它們不再與您的業務邏輯糾纏在一起,您將擁有更輕松的時間。

例如, 您可以從Howardboost 竊取一個實現

使用示例:

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM