folly下AtomicHashArray和AtomicHashMap简介

来源:互联网 发布:python 关闭tcp连接 编辑:程序博客网 时间:2024/05/21 19:23

AtomicHashArray类

主要功能介绍:

主要有

 std::atomic<int64_t> isFull_; // Used by insertInternal

 std::atomic<int64_t> numErases_;  // Successful key erases

std::atomic<KeyT> *cellkeyptr_; //cell中key的指针。

三种原子类型

 

维护一张哈希表的创建、插入、查询、删除(标记为erase并没有实际删除)。

哈希数据映射对,存放在cell_[]中,下标是通过hash算法计算出来的哈希值(具体插入位置还有可能通过probe线性探测位移得到)。

Cell[]中存放的是[Key,value]结构的pair。

Key只支持int型整数(使用其他类型可以先将其他类型转换为整数)。

引入原子操作,支持并发处理。

主要功能函数:

create()

申请哈希表所需的空间,初始化哈希表中的所有key为kEmptykey_。在初始化每个key值时,采用的是原子操作。(初始化用原子操作,难道会有多个线程同时初始化一个哈希表?)

findInternal ()

查询操作,最终所有的查询方式都会调用到此函数中,进行哈希查询。(查询操作应该不需要同步处理吧?)

 

根据key_in计算哈希下标。取下标中的key和key_in比较(取下标中对应的key用的是原子操作,这个原子操作是为了防止什么情况的发生?)。

 

如果key为kEmptyKey_,查询结果不存在。

 

如果key不为kEmptyKey_,且key和key_in相等,查找成功。

 

如果key不为kEmptyKey_,且key和key_in不相等,则线性遍历。直到上述情况出现,或者遍历结束都没找到。

erase ()

根据key_in计算哈希下标,原子操作取下标对应的key值(这个能解决什么冲突情况呢?)

 

判断取出的key值是否为kEmptyKey_或者kLockedKey(正在被插入)_。两种情况都说明key不存在。

 

如果key和key_in相等。原子操作将key设置为kErasedKey_(这个就算多线程同时设置为kErasedKey_会有问题吗?)。numErases_原子曾加1(这个方式多线程同时加出现的冲突问题)。此时只是将key标记为kErasedKey,并没有释放内存。主要原因是查询的时候将value指针给了用户,释放内存会出现野指针导致内核崩溃等问题。(这样的话,如果删除操作过于频繁的话,所需要的内存岂不是越来越大。这种处理方式不能够支持程序长期跑下去吧?)

 

如果key和key_in不相等。线性遍历。直到上述情况出现,或者遍历结束都没找到。

insertInternal()

插入操作最终都会调入到此函数中来,进行插入。

 

根据key_in计算哈希下标,原子操作取下标对应的key值,判断此key是否为kEmptyKey_

 

如果key为空,原子操作取isFull_,判断哈希表是否插满。

 

如果插满了,atomic_hash_spin_wait自旋锁等待其他正在插入的线程也都通过这里,然后设置isFull_为NO_PENDING_INSERTS,并返回插入失败,告诉AtomicHashMap此submap已经不能插入了。

 

如果没有满,则原子操作锁上当前的cell(保证只有一个进程能对这个cell进行插入操作,就看明白这个地方为什么用原子操作了,其他大多原子操作没看太懂是为了避免什么样的情况发生的)。插入操作结束后对cell解锁(上锁即将key设为lock状态,解锁就是将key插入新的key_in)。如果插入后已经插入的元素>=最大元素时,标记isFull_为true。

 

如果key不为空,cell还在被锁定,说明其他线程还在插入这个cell,等待其他线程插入完成。然后判断key和key_in是否相等

 

         如果相等,本次插入失败,已经插入过。

  

如果不相等,如果key 为kEmptyKey_或者kLockedKey_说明之前有插入失败或还再插入的情况,那么continue等待。其他情况下进行线性遍历下一个cell。

AtomicHashMap类

主要功能:

主要有

  std::atomic<SubMap*>subMaps_[kNumSubMaps_]; 指向AtomicHashArray

 std::atomic<uint32_t> numMapsAllocated_; 记录AtomicHashArray数目

两种原子类型

 

AtomicHashMap主要用来对AtomicHashArray进行管理

主要功能函数:

AtomicHashMap ()

构造函数,初始化submap[0](原子操作,难道这个初始化也会交由多线程处理?),并初始化submap[subMapCount]后面的都指向空。

findInternal ()

首先原子操作获取subMaps_[0](获取操作有必要原子操作吗?),进行查询,如果没查询到,原子获取numMaps(Map数目),对只有的每个map进行依次查询,直到查到或者全部遍历或者没查到。

erase ()

原子操作依次加载每个map,进行查询删除操作。

insertInternal()

原子操作依次加载每个map,进行插入操作。如果全部插入满了,需要重新分配一个新的map进行插入操作。在重新分配时会调用atomic_hash_spin_wait(Cond condition)等待,分配好新的submap后再进行插入。

总结

对哈希表的操作主要还是在AtomicHashArray类中。根据上面的主要功能的分析情况,结合自己的想法:

1、  我觉得查询操作应该不需要同步机制,哈希表的多线程查询应该是没有问题的,没看懂AtomicHashArray中这句

  const KeyT key =acquireLoadKey(cells_[idx]);

   为什么需要原子操作取出下标对应的key?当然这样做也没什么错。

2、  erase()删除操作中,其实并不是我们传统的删除,只是将要删除的cell中的key值设置为erase标志来代表已经被删除掉了。但是实际上空间并没有释放,也不可能再次插入。这样的设定,对空间的要求应该很高吧,如果程序长期运行的话。

3、  insertInternal()插入过程中,也是除了在要对cell进行写入的时候应该上锁,别的地方,不用原子操作也是可以的吧,没看出来别的地方的原子操作时为了确保什么样的情况。

4、  其中锁的实现是使用此函数:

compare_exchange_strong(expect, kErasedKey_)) {

      numErases_.fetch_add(1, std::memory_order_relaxed);

//当前值与期望值相等时,修改当前值为设定值,返回true

         //当前值与期望值不等时,将期望值修改为当前值,返回false

         //整个操作是原子的,在某个线程读取和修改该原子对象时,另外的线程不能对读取和修改该原子对象。

 

望有兴趣的朋友进行解答补充

2 0