簡體   English   中英

嘗試創建線程安全的std :: map

[英]Attemption to create thread safe std::map

假設我們有std::map容器,並且希望使其在插入,擦除,搜索和編輯記錄方面安全。 同時,我們希望線程可以並行處理不同的記錄(讀取和編輯記錄)。 為此,我制作了一個單獨的記錄類-編輯操作,該操作受互斥鎖保護。

class Data
{
public:
    Data(const std::string& data) : _mutex(), _data(data) { }
    void setData(const std::string& data)
    {
        std::lock_guard<std::mutex> locker(_mutex);
        _data = data;
    }

    const std::string& getData() const { return _data; }

private:
    std::mutex _mutex;
    std::string _data;
};

class Storage
{
public:
    void insertData(size_t key, const std::string& data)
    {
        std::lock_guard<std::mutex> locker(_mutex);
        _storage[key] = data;
    }

    void eraseData(size_t key)
    {
        std::lock_guard<std::mutex> locker(_mutex);
        _storage.erase(key);
    }

    const std::string& getData(size_t key) const { return _storage[key].getData(); }

    void setData(size_t key, const std::string& data) { _storage[key].setData(data); }

private:
    std::mutex _mutex;
    std::map<size_t, Data> _storage;
};

現在,假設線程抓取要編輯的某些記錄的“本地”互斥量( Data::setData方法調用)。 同時,其他線程將“全局”互斥鎖刪除,以刪除該記錄(調用Storage::eraseData方法)-有什么問題嗎? 此代碼中還有哪些其他問題?

首先解決您的並發問題。 這是一個C ++ 14解決方案,因為C ++ 11版本更為冗長,並且我們沒有想要的所有鎖定原語:

template<class T>
struct thread_safe {
  template<class F>
  auto read( F&& f ) const {
    std::shared_lock<decltype(mutex)> lock(mutex);
    return std::forward<F>(f)(t);
  }
  template<class F>
  auto write( F&& f ) {
    std::unique_lock<decltype(mutex)> lock(mutex);
    return std::forward<F>(f)(t);
  }
  template<class O>
  thread_safe(O&&o):t(std::forward<O>(o)) {}

  thread_safe() = default;

  operator T() const {
    return o.read([](T const& t){return t;});
  }

  // it is really this simple:
  thread_safe( thread_safe const& o ):t( o ) {}

  // forward to above thread safe copy ctor:
  thread_safe( thread_safe & o ):thread_safe( const_cast<thread_safe const&>(o) ) {}
  thread_safe( thread_safe && o ):thread_safe(o) {}
  thread_safe( thread_safe const&& o ):thread_safe(o) {}

  thead_safe& operator=( thread_safe const& o ) {
    write( [&o](auto& target) {
      target = o;
    });
    return *this;
  } 
  template<class O>
  thread_safe& operator=( O&& o ) {
    write([&o](auto& t){ t = std::forward<O>(o); });
    return *this;
  }
private:
  T t;
  mutable std::shared_timed_mutex mutex;
};

這是圍繞任意類的線程安全包裝器。

我們可以直接使用它:

typedef thread_safe< std::map< size_t, thread_safe<std::string> > > my_map;

這里我們有兩級線程安全映射。

使用示例,將條目33設置為"hello"

my_map.write( [&](auto&& m){
  m[33] = "hello";
} );

在每個元素和整個地圖上都有許多讀者,一位作家。 從返回的迭代器readwrite呼叫是不是安全。

當然,您應該測試和審核上面的代碼。 我沒有

核心思想很簡單。 要閱讀,你必須.read線程安全的對象。 您傳入的Lambda會獲得對基礎數據的const& std::數據上,保證這些數據是多讀取器安全的。

要編寫,您必須.write 這將獲得排他鎖,從而阻止其他.read 此處的lambda對基礎數據獲取&

我添加了operator T=以及copy-construct,以使類型更規則。 這樣做的代價是您可能會意外地產生很多鎖定/解鎖行為。 優點是m[33] = "hello" 可以正常工作 ,真棒。

您有兩個大問題:

  1. 如果一個線程同時調用insertData ,而另一個線程調用getData什么呢? 調用operator[]可能會崩潰,因為嘗試訪問地圖時正在對其進行修改。

  2. 如果一個線程調用eraseData而另一個線程仍在使用從getData返回的引用,會發生什么情況? 該引用可能無效,從而導致崩潰。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM