RAII 线程安全获取器
Posted
技术标签:
【中文标题】RAII 线程安全获取器【英文标题】:RAII thread safe getter 【发布时间】: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 而不会锁定资源。
【参考方案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 我希望现在更有意义。 原来我过早地赞成这个。不幸的是它没有编译。第一个问题是LockedResource
s 移动构造函数,这里使用std::lock_guard
无效,不可移动。用std::unique_lock
替换它可以解决这个问题。下一个问题是A::getResource()
的返回类型,它本质上导致返回对本地临时的引用。将其替换为 LockedResource<Mutex, Resource>
即可解决此问题。 .您能否编辑您的答案以使其有效?谢谢!
@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<ResourceGuard>
返回守卫。 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& m, Resource& r)
。这样就可以就地构建锁。
当然这可能更整洁。无论如何,多余的移动很可能会被优化掉。以上是关于RAII 线程安全获取器的主要内容,如果未能解决你的问题,请参考以下文章