【发布时间】:2015-08-20 17:15:00
【问题描述】:
我想编写一个包装器,允许以独占方式签出资源并在持有者超出范围时自动返回资源。
(多线程)客户端代码中的用法是这样的:
{
auto myResource(_manager.checkoutExclusive());
doStuffWithoutEverStoringTheValue(myResource.val());
} //resource is returned to _manager
我知道将 IAlgorithm 交给 _manager 以在资源上执行会更干净,但目前这对我来说不是最佳解决方案。
我还想提供一个并行签出选项,允许多个并发签出(例如,用于只读访问,或者如果客户端处理资源本身的线程安全)。当然并行结账和排他结账不能混用。
以下是我解决此问题的尝试。除了我无法避免的重复代码(const/non-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;
};
【问题讨论】:
-
整个流控
_returnTo不是线程安全的。 -
但是 ExclusiveCheckout 只存在于一个线程中,不是吗?
标签: c++ multithreading templates c++11 thread-safety