前提
1、map 的 key 是可以穷举的
2、允许修改已经删除的 value,我们认为是创建了新 value 再修改
思路
1、先把所有的 key 放入 map 中,这里我们获取 key 的时候 100% 不会报错 (前提1)
key 永不删除,如果要删除 key,会体现在 value 上,见下文
2、value 值为原子变量,比如 c++ 有 atomic
atomic<int> flag = 0;
我们获取 value 的时候都是原子操作,不会和其他线程共享导致出问题
3、map 的 value 我们声明为一个特殊结构体(mapValue),包含两部分,一个为标记位,一个为真正的 value 值
struct mapValue{
int flag; // 标记位
int value; // 真正的 value,可以是任意类型
};
4、标记位有两个状态,一个是删除状态,代表该值已经被删除;另外一个是可用状态,代表该 value 可以正常使用
使用方法如下
temp=m[key] //获取 map 的 key 对应的 value 时,务必请先拷贝至本地,temp 类型为 mapValue
if (temp.flag==1)
// 如果到这里 m[key].flag 被改为 0 了,没有风险,因为我们有备份,最次也是读到脏数据
value=temp.value
else
//该key不存在,可能被删了
// 如果是下面这样可能会出问题 (没拷贝就直接使用)
if (m[key].flag==1)
// 如果到这里 m[key].flag 被改为 0 了,就有风险,因为 m[key].value 可能会被连带修改
value=m[key].value
else
//该key不存在,可能被删了
mapValue 拷贝可以直接转成等长度的 char 数组结构体整体拷贝,比如
struct newMapValue{
unsigned char[8] value;
};
修改和删除,就是新建 mapValue,然后再整体覆盖原有的 map 的 mapValue(前提2)
这样,无锁 map 的增删改查都没问题了
其他
1、如果要去除前提一的话,需要对 map 的桶原子化操作,因为我们知道,key 确定了,哈希 key 对应桶的位置也会确定,所以可以通过对桶原子化操作来避免线程不安全,但是这样会极大影响性能。并且这样以来会带来一个问题,就是 map 扩容会影响桶的元素,所以又会有新的限制,就是我们申请的 map 空间要足够大,避免 map 的扩容。前提一的存在,避免了上面说的情况。
2、前提二其实不是很影响使用,不去除没关系