从类返回自动解锁的锁定资源

Return locked resource from class with automatic unlocking

本文关键字:锁定 资源 解锁 返回      更新时间:2023-10-16

我想有一个类成员函数返回一个指向资源的指针。资源应该自动锁定和解锁。我想创建一个不可复制的对象来处理锁。

你认为以下是一个好的解决方案吗?是线程安全的吗?STL中已经有这个用例的工具了吗?

template<typename T, typename M>
struct LockedResource
{
private:
    T* data_;
    std::unique_lock<std::mutex> lock_;
public:
    LockedRessource(T* data, M& mtx) : data_{data}, lock_{mtx} {}
    T* data() const { return data_; }
};

用例示例:

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
class Foo {
private:
    std::vector<int> data_;
    std::mutex mtx_;
public:
    LockedResource<std::vector<int>,std::mutex> data()
    { return LockedResource<std::vector<int>,std::mutex>{&data_, mtx_}; }
};
Foo foo;
void worker(int worker, int iterations, int dt) {
    for(int i=0; i<iterations; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(dt));
        auto res = foo.data();
        // we now have a unique_lock until the end of the scope
        std::cout << "Worker " << worker << " adding " << i << std::endl;
        res.data()->push_back(i);
    }
}
int main() {
    std::thread t1{worker, 1, 10, 173};
    std::thread t2{worker, 2, 20, 87};    
    t1.join();
    t2.join();
}

这个想法——提供一个句柄来封装对同步对象的访问和必要的锁定——并不是新的:参见使用同步值强制正确使用互斥锁。

我喜欢Rook使用带有自定义删除器的unique_ptr作为句柄的想法,所以我想出了一个:

template <typename BasicLockable>
class unlock_deleter {
    std::unique_lock<BasicLockable> lock_;
public:
    unlock_deleter(BasicLockable& mtx) : lock_{mtx} {}
    unlock_deleter(BasicLockable& mtx, std::adopt_lock_t a) noexcept : lock_{mtx, a} {}
    template <typename T>
    void operator () (T*) const noexcept {
        // no-op
    }
};
template <typename T, typename M = std::mutex>
using locked_ptr = std::unique_ptr<T, unlock_deleter<M>>;

,用于实现包装对象和互斥锁的模板类synchronized:

template <typename T, typename M = std::mutex>
class synchronized {
  T item_;
  mutable M mtx_;
  // Implement Copy/Move construction
  template <typename Other, typename N>
  synchronized(Other&& other, const std::lock_guard<N>&) :
    item_{std::forward<Other>(other).item_} {}
  // Implement Copy/Move assignment
  template <typename Other>
  void assign(Other&& other) {
    std::lock(mtx_, other.mtx_);
    std::lock_guard<M> _{mtx_, std::adopt_lock};
    std::lock_guard<decltype(other.mtx_)> _o{other.mtx_, std::adopt_lock};
    item_ = std::forward<Other>(other).item_;
  }
public:
  synchronized() = default;
  synchronized(const synchronized& other) :
    synchronized(other, std::lock_guard<M>(other.mtx_)) {}
  template <typename N>
  synchronized(const synchronized<T, N>& other) :
    synchronized(other, std::lock_guard<N>(other.mtx_)) {}
  synchronized(synchronized&& other) :
    synchronized(std::move(other), std::lock_guard<M>(other.mtx_)) {}    
  template <typename N>
  synchronized(synchronized<T, N>&& other) :
    synchronized(std::move(other), std::lock_guard<N>(other.mtx_)) {}    
  synchronized& operator = (const synchronized& other) & {
    if (&other != this) {
      assign(other);
    }
    return *this;
  }
  template <typename N>
  synchronized& operator = (const synchronized<T, N>& other) & {
    assign(other);
    return *this;
  }
  synchronized& operator = (synchronized&& other) & {
    if (&other != this) {
      assign(std::move(other));
    }
    return *this;
  }
  template <typename N>
  synchronized& operator = (synchronized<T, N>&& other) & {
    assign(std::move(other));
    return *this;
  }
  template <typename N>
  void swap(synchronized<T, N>& other) {
    if (static_cast<void*>(&other) != static_cast<void*>(this)) {
      std::lock(mtx_, other.mtx_);
      std::lock_guard<M> _{mtx_, std::adopt_lock};
      std::lock_guard<N> _o{other.mtx_, std::adopt_lock};
      using std::swap;
      swap(item_, other.item_);
    }
  }
  locked_ptr<T, M> data() & {
    return locked_ptr<T, M>{&item_, mtx_};
  }
  locked_ptr<const T, M> data() const& {
    return locked_ptr<const T, M>{&item_, mtx_};
  }
};
template <typename T, typename M, typename N>
void swap(synchronized<T, M>& a, synchronized<T, N>& b) {
  a.swap(b);
}

注意正确同步拷贝/移动/交换。以下是Coliru的示例程序。