我想编写一个包装器,允许专门检出资源,并在持有者超出范围后自动返回资源。
(多线程)客户端代码中的用法如下:
{
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;
};
答案 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_mutex
和shared_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