我有一个线程类,我偶尔会从中获取一个指针实例变量。我希望通过互斥锁保护此访问权限,以便阻止线程访问此资源,直到客户端完成其指针。
我最初的方法是返回一对对象:一个是指向资源的指针,另一个是指向互斥锁上的锁定对象的shared_ptr。此shared_ptr包含对锁对象的唯一引用,因此当超出范围时,应该解锁互斥锁。像这样:
void A::getResource()
{
Lock* lock = new Lock(&mMutex);
return pair<Resource*, shared_ptr<Lock> >(
&mResource,
shared_ptr<Lock>(lock));
}
此解决方案不太理想,因为它要求客户端保留整个对象。这样的行为打破了线程的安全性:
Resource* r = a.getResource().first;
另外,我自己的实现是死锁,我很难确定原因,所以可能还有其他问题。
我想要的是一个shared_ptr,它包含锁作为实例变量,并将其与访问资源的方法绑定在一起。这似乎应该有一个既定的设计模式,但做了一些研究,我很惊讶地发现它很难找到。
我的问题是:
(注意我正在开发一个使用Qt的代码库但不幸的是在这种情况下不能使用boost。但是,涉及提升的答案仍然是普遍感兴趣的。)
答案 0 :(得分:7)
我不确定是否有任何标准实现,但由于我喜欢无缘无故重新实现的东西,这里有一个应该有效的版本(假设你不想复制这样的指针):< / p>
template<class T>
class locking_ptr
{
public:
locking_ptr(T* ptr, mutex* lock)
: m_ptr(ptr)
, m_mutex(lock)
{
m_mutex->lock();
}
~locking_ptr()
{
if (m_mutex)
m_mutex->unlock();
}
locking_ptr(locking_ptr<T>&& ptr)
: m_ptr(ptr.m_ptr)
, m_mutex(ptr.m_mutex)
{
ptr.m_ptr = nullptr;
ptr.m_mutex = nullptr;
}
T* operator ->()
{
return m_ptr;
}
T const* operator ->() const
{
return m_ptr;
}
private:
// disallow copy/assignment
locking_ptr(locking_ptr<T> const& ptr)
{
}
locking_ptr& operator = (locking_ptr<T> const& ptr)
{
return *this;
}
T* m_ptr;
mutex* m_mutex; // whatever implementation you use
};
答案 1 :(得分:5)
您正在描述Kevlin Henney在EXECUTE AROUND POINTER中描述的Executing Around Sequences模式的变体。
我在exec_around.h
有一个原型实现,但我不能保证它在所有情况下都能正常工作,因为它正在进行中。它包含一个函数mutex_around
,它创建一个对象并将其包装在智能指针中,该指针在访问时锁定和解锁互斥锁。
答案 2 :(得分:0)
这里有另一种方法。远不那么灵活,不那么通用,但也简单得多。虽然它似乎仍然适合您的确切情况。
shared_ptr
(standard和Boost)提供构建它的方法,同时提供另一个shared_ptr
实例,该实例将用于使用计数器和一些不会使用的任意指针完全可以管理。在cppreference.com上,它是第8种形式(the aliasing constructor)。
现在,通常,此表单用于转换 - 例如从派生类对象向基类对象提供shared_ptr
。它们共享所有权和使用计数器,但(通常)具有两种不同类型的不同指针值。此表单还用于向shared_ptr
基于shared_ptr
的成员值提供auto A::getResource()
{
auto counter = std::make_shared<Lock>(&mMutex);
std::shared_ptr<Resource> result{ counter, &mResource };
return result;
}
,以对象为其成员。
我们可以在这里滥用&#34;提供锁定防护的形式。这样做:
shared_ptr
返回的mResource
指向mMutex
,只要被任何人使用,就会锁定mResource
。
此解决方案的问题在于,您现在有责任确保mMutex
保持有效(特别是 - 它不会被销毁)。如果锁定counter
足够了,那么你没事。
否则,必须根据您的特定需求调整上述解决方案。例如,您可能希望struct
是一个简单的Lock
,使shared_ptr
和另一个A
同时拥有mResource
对象拥有{{1} }}