This post描述了一种无锁的32位哈希表算法。算法的核心是无锁线性搜索,用于在(逻辑)列表中插入key-val对:
以下是提供的代码:
void ArrayOfItems::SetItem(uint32_t key, uint32_t value)
{
for (uint32_t idx = 0;; idx++)
{
uint32_t prevKey = mint_compare_exchange_strong_32_relaxed(&m_entries[idx].key, 0, key);
if ((prevKey == 0) || (prevKey == key))
{
mint_store_32_relaxed(&m_entries[idx].value, value);
return;
}
}
}
对于特定问题,我需要在表中插入随机键 - 值对。因此,我需要至少64位密钥,因为对于32位,在65536次插入后有50%的冲突几率,这太低了。不幸的是,我do not have 64位cmpxchg作为原语。
是否可以将上面的哈希表一般化为64位密钥,仅使用32位cmpxchg?
答案 0 :(得分:2)
我不确定你是否仍然想要保持无锁特性,或者只想获得64位密钥/值存储和放大器。运行。 (?)
@kol在这里发布了一个64位的MurmurHash3: hashing a small number to a random looking 64 bit integer
显然,如果您引入了第二个数组来声明密钥位置所有权,并且对于值存储有所尊重,那么您可以分两步读取CAS 64位值,然后释放所有权。当然,这并不能让你无锁。
---------------编辑:---------------
从2007年开始,作者在哈希表上至少有两个视频:
编程语言高级主题:无锁哈希表 https://www.youtube.com/watch?v=HJ-719EGIts
快速无等待哈希表
https://youtu.be/WYXgtXWejRM
他将程序流程与有限状态机相关联。忽略增长表的问题,在将潜在突变应用于该位置之前,可以存在四个状态。键/值= [nil / nil],[X / nil],[X / X],[nil / X]。
在准备变异时读取状态并不能保证在并发时状态在应用变异时保持不变。
通过32位操作,我们有以下逻辑:
- 如果读取键=所需的键,则可以将值写入该位置
- 如果读取键= nil,值=非nil,则另一个线程正在改变位置
- 如果读取密钥= nil,且值= nil,则可以通过成功的密钥CAS写入该位置。
如果要使用32位原子操作来存储64位数据而不进行锁定,则状态图的大小会增加,故障状态会更多,例如:
- 您可以阅读半创建的密钥
- 一个值条目的一半的CAS更新可能被另一个线程踩踏,在第二个CAS上失败
- 一个密钥条目的一半的CAS创建可能被另一个线程踩踏,在第二个CAS上失败
- 数组初始化'nil'的32位表示应排除在64位密钥的一半或值之外
增加表大小的过程还增加了一些要考虑的状态。