多线程unordered_map

时间:2018-03-12 16:55:54

标签: c++ multithreading c++11 hashtable

我在多线程环境中工作。基本上,我有一个unordered_map,可以同时由多个线程访问。现在,我的算法是:

function foo(key) {
  scoped_lock()
  if key exists {
    return Map[key]
  }

  value = get_value()
  Map[key] = value
}

显然,这种实现的表现并不好。我可以使用任何算法/方法来提高性能吗?

修改

我做了很多测试,我想到了双重检查锁定。所以,我修改了代码:

function foo(key) {
  if key exists {
    return Map[key]
  }

  scoped_lock()
  if key exists {
    return Map[key]
  }

  value = get_value()
  Map[key] = value
}

实际上我只在scoped_lock()之前添加了另一个检查。在这种情况下,假设函数被调用N次。如果第一次m来电foom < N填充了地图,而下一次N - m来电只能从地图中获取值,我就不需要了独家访问。此外,在scoped_lock之后还有另一项检查,以确保线程安全。我对吗?在任何情况下,使用第一个代码执行需要~208s,而第二个代码需要~200s。

1 个答案:

答案 0 :(得分:2)

这是一个实用程序类:

template<class T, class M=std::mutex, template<class...>class S=std::unique_lock, template<class...>class U=std::unique_lock>
struct mutex_protected {
  template<class F>
  auto read( F&& f ) const
  -> typename std::result_of<F&&(T const&)>::type
  {
    auto l = lock();
    return std::forward<F>(f)(data);
  }
  template<class F>
  auto write( F&& f ) 
  -> typename std::result_of<F&&(T&)>::type
  {
    auto l = lock();
    return std::forward<F>(f)(data);
  }
  mutex_protected(mutex_protected&&)=delete;
  mutex_protected& operator=(mutex_protected&&)=delete;

  template<class...Args>
  mutex_protected( Args&&...args ):
    data( std::forward<Args>(args)... )
  {}
private:
  mutable M m;
  T data;

  U<M> lock() { return U<M>(m); }
  S<M> lock() const { return S<M>(m); }
};

它,特别是在中,允许您以易于编写的方式与互斥锁保护的数据实例进行交互。

中,您可以使用std::shared_timed_mutex,在中,您可以使用std::shared_mutex,如下所示:

template<class T>
using rw_guarded = mutex_guarded< T, std::shared_mutex, std::shared_lock >;

这使得能够同时拥有许多读者。但是你应该首先确定简单的互斥锁是否足够快。

struct cache {
  using Key=std::string;
  using Value=int;
  using Map=std::unordered_map< Key, Value >;
  Value get( Key const& k ) {
    Value* r = table.read([&](Map const& m)->Value*{
      auto it = m.find(k);
      if (it == m.end()) return nullptr;
      return std::addressof( it->second );
    });
    if (r) return *r;
    return table.write([&](Map& m)->Value{
      auto it = m.find(k);
      if (it != m.end()) return it->second;
      auto r = m.insert( std::make_pair(k, 42) ); // construct data here
      return r.first->second;
    });
  }
private:
  mutex_guarded< std::unordered_map< Key, Value > > table;
};

mutex_guarded升级到rw_guarded,然后切换到读写器锁。

这是一个更复杂的版本:

有两张地图;一个是重要的,一个是共享的价值未来。

使用读写器锁(又名共享互斥锁)。

获取,获取共享锁。检查它是否存在。如果是,请返回。

解锁第一张地图。锁定第二张地图进行书写。如果密钥下方还没有共享的未来,请添加一个。解锁地图2,并等待共享的未来,无论您是否添加它。

完成后,锁定第一张地图进行阅读;检查结果是否已存在。如果是,请将其退回。如果没有,解锁,重新锁定写入,如果还没有数据移动到地图1,则在第一张地图中返回数据。

这旨在最大限度地减少周期映射1被独占锁定,允许最大并发性。

其他设计将优化其他考虑因素。

使用operator[]与任何地图互动而没有某种活动的锁定。知道哪些锁对应于哪个地图。请注意,在某些情况下,可以在没有锁定的情况下完成读取元素(不查找)。有时需要阅读共享内容的副本,而不是共享内容。查找每种类型的文档以确定哪些操作需要锁定。