15

我有一个线程类,我想偶尔从中获取一个实例变量的指针。我希望这种访问受到互斥锁的保护,以便在客户端完成其指针之前阻止线程访问此资源。

我最初的方法是返回一对对象:一个是指向资源的指针,一个是指向互斥锁对象的 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 ,它包含作为实例变量的锁,将它与访问资源的方法绑定。这似乎应该有一个既定的设计模式,但做了一些研究后,我惊讶地发现它很难遇到。

我的问题是:

  • 这种模式有共同的实现吗?
  • 将互斥锁放在我忽略的 shared_ptr 中是否存在问题,从而阻止了这种模式的广泛传播?
  • 是否有充分的理由不实现我自己的 shared_ptr 类来实现这种模式?

(注意,我正在开发一个使用 Qt 的代码库,但不幸的是在这种情况下不能使用 boost。但是,涉及 boost 的答案仍然是普遍感兴趣的。)

4

4 回答 4

7

我不确定是否有任何标准实现,但由于我喜欢无缘无故地重新实现东西,所以这里有一个应该可以工作的版本(假设你不想复制这样的指针):

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
};
于 2013-04-08T10:22:40.650 回答
6

您正在描述由 Kevlin Henney 在Executing Around Sequences中描述的EXECUTE AROUND POINTER模式的变体。

我有一个原型实现,exec_around.h但我不能保证它在所有情况下都能正常工作,因为它正在进行中。它包括一个函数,该函数mutex_around创建一个对象并将其包装在一个智能指针中,该指针在访问时锁定和解锁互斥锁。

于 2013-04-08T11:19:20.400 回答
1

这里还有另一种方法。灵活性和通用性要低得多,但也简单得多。虽然它似乎仍然适合您的确切情况。

shared_ptr标准Boost)提供了构造它的方法,同时提供了另一个shared_ptr实例,该实例将用于使用计数器和一些根本不会被管理的任意指针。在cppreference.com上,它是第 8 种形式(别名构造函数)。

现在,通常,这种形式用于转换——比如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;
}

返回的shared_ptr指向mResource并保持mMutex锁定,只要它被任何人使用。

此解决方案的问题在于,您现在有责任确保在这段时间内mResource保持有效(特别是 - 它不会被破坏)。如果锁定mMutex就足够了,那么你很好。

否则,上述解决方案必须根据您的特定需求进行调整。例如,您可能希望拥有counter一个简单的对象,该对象struct同时保留了拥有.Lockshared_ptrAmResource

于 2018-05-03T23:31:42.833 回答
1

要添加到Adam Badura 的答案,对于使用std::mutexand的更一般情况std::lock_guard,这对我有用:

auto A::getResource()
{
    auto counter = std::make_shared<std::lock_guard<std::mutex>>(mMutex);
    std::shared_ptr<Resource> ptr{ counter, &mResource} ;
    return ptr;
}

其中 和 的生命周期std::mutex mMutexResource mResource某个类管理A

于 2021-11-12T16:36:09.353 回答