【问题标题】:RAII thread safe getterRAII 线程安全获取器
【发布时间】:2015-11-12 15:21:48
【问题描述】:

大多数时候我在代码中看到线程安全 getter 方法的这种实现的一些变体:

class A
{
public:

    inline Resource getResource() const
    {
        Lock lock(m_mutex);

        return m_resource;
    }

private:
    Resource m_resource;
    Mutex    m_mutex;
};

假设类 Resource 不能被复制,或者复制操作的计算成本太高,C++ 中有没有办法避免返回副本但仍然使用 RAII 风格的锁定机制?

【问题讨论】:

  • 您使用的是哪个 C++ 版本?
  • “有没有办法避免退回副本”是的,根本不退回。
  • 返回const Resource&?
  • @cad 那不是线程安全的
  • @cad 返回引用的问题是,在通过 RAII 调用 getResource 后锁被破坏,现在获得该引用的任何人都可以访问 m_resource 而不会锁定资源。

标签: c++ multithreading locking getter raii


【解决方案1】:

我还没有尝试过,但是这样的东西应该可以工作:

#include <iostream>
#include <mutex>
using namespace std;

typedef std::mutex Mutex;
typedef std::unique_lock<Mutex> Lock;

struct Resource {
    void doSomething() {printf("Resource::doSomething()\n"); }
};

template<typename MutexType, typename ResourceType>
class LockedResource
{
public:
    LockedResource(MutexType& mutex, ResourceType& resource) : m_mutexLocker(mutex), m_pResource(&resource) {}
    LockedResource(MutexType& mutex, ResourceType* resource) : m_mutexLocker(mutex), m_pResource(resource) {}
    LockedResource(LockedResource&&) = default;
    LockedResource(const LockedResource&) = delete;
    LockedResource& operator=(const LockedResource&) = delete;

    ResourceType* operator->()
    {
        return m_pResource;
    }

private:
    Lock m_mutexLocker;
    ResourceType* m_pResource;
};

class A
{
public:

    inline LockedResource<Mutex, Resource> getResource()
    {
        return LockedResource<Mutex, Resource>(m_mutex, &m_resource);
    }

private:
    Resource m_resource;
    Mutex    m_mutex;
};


int main()
{
    A a;
    { //Lock scope for multiple calls
        auto r = a.getResource();
        r->doSomething();
        r->doSomething();

        // The next line will block forever as the lock is still in use
        //auto dead = a.getResource();

    } // r will be destroyed here and unlock
    a.getResource()->doSomething();
    return 0;
}

但要小心,因为被访问资源的生命周期取决于所有者的生命周期 (A)


Godbolt 示例:Link

P1144 很好地减少了生成的程序集,因此您可以看到锁在哪里锁定和解锁。

【讨论】:

  • 无法理解 main() 中嵌套作用域的原因
  • @nyarlathotep108 该范围是为了指示锁的开始和结束位置,但我没有存储 LockedRessource 的实例,因此搞砸了。忽略它。我目前正在使用移动设备,但明天我将删除该范围。
  • @nyarlathotep108 我希望现在更有意义。
  • 原来我过早地赞成这个。不幸的是它没有编译。第一个问题是LockedResources 移动构造函数,这里使用std::lock_guard 无效,不可移动。用std::unique_lock 替换它可以解决这个问题。下一个问题是A::getResource() 的返回类型,它本质上导致返回对本地临时的引用。将其替换为 LockedResource&lt;Mutex, Resource&gt; 即可解决此问题。 .您能否编辑您的答案以使其有效?谢谢!
  • @ahans 就像我提到的,我没有测试过代码,它更像是某种模板。感谢您指出缺陷 - 我现在修复了提到的问题。
【解决方案2】:

返回一个为Resource 类提供线程安全接口和/或保留一些锁的访问器对象怎么样?

class ResourceGuard {
private:
    Resource *resource;

public:
    void thread_safe_method() {
        resource->lock_and_do_stuff();
    }
}

这将以 RAII 方式清除,并在需要时释放任何锁。如果您需要锁定,则应在 Resource 类中完成。

当然你要注意Resource的寿命。一个非常简单的方法是使用std::shard_ptr。一个weak_ptr 也可能适合。

【讨论】:

  • 打败我。就个人而言,我会存储对m_resource 的引用而不是指针,并通过std::unique_ptr&lt;ResourceGuard&gt; 返回守卫。 std::shard_ptr 可以复制到其他线程,并且拥有相同互斥锁的许多线程将是一场灾难。
  • 您能否将答案中的代码与吸气剂代码整合在一起?如果我每次都返回一个 ResourceGuard 对象并且它内部有自己的互斥锁,我看不出这是如何工作的......
  • 你当然是对的。访问接口时要加锁。
【解决方案3】:

实现相同目的的另一种方法。这是可变版本的实现。 const 访问器同样简单。

#include <iostream>
#include <mutex>

struct Resource
{

};

struct locked_resource_view
{
    locked_resource_view(std::unique_lock<std::mutex> lck, Resource& r)
    : _lock(std::move(lck))
    , _resource(r)
    {}

    void unlock() {
        _lock.unlock();
    }

    Resource& get() {
        return _resource;
    }

private:
    std::unique_lock<std::mutex> _lock;
    Resource& _resource;
};

class A
{
public:

    inline locked_resource_view getResource()
    {
        return {
            std::unique_lock<std::mutex>(m_mutex),
            m_resource
        };
    }

private:
    Resource m_resource;
    mutable std::mutex    m_mutex;
};

using namespace std;

auto main() -> int
{
    A a;
    auto r = a.getResource();
    // do something with r.get()

    return 0;
}

【讨论】:

  • 现在这更接近我即将发布的内容(比 ssteinberg 的原始答案)。我会这样定义构造函数:locked_resource_view(std::mutex&amp; m, Resource&amp; r)。这样就可以就地构建锁。
  • 当然这可能更整洁。无论如何,多余的移动很可能会被优化掉。
猜你喜欢
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多