RAII 线程安全 getter

RAII thread safe getter

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

class A
{
public:

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

        return m_resource;
    }

private:
    Resource m_resource;
    Mutex    m_mutex;
};

假设class Resource无法复制,或者复制操作的计算成本过高,C++有没有办法避免返回的副本但仍在使用 RAII 样式的锁定机制?

返回一个为 Resource class and/or 提供线程安全接口的访问器对象如何保持一些锁定?

class ResourceGuard {
private:
    Resource *resource;

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

这将以 RAII 方式清除,并在需要时释放任何锁。如果您需要锁定,应该在 Resource class.

中完成

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

实现相同目的的另一种方法。这是可变版本的实现。 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;
}

我没试过,但像这样的东西应该有用:

#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 把生成的程序集缩减得很好,这样你就可以看到锁在哪里锁了,在哪里解锁了。