无锁的原子函数改变两个独立的内存位置
Posted
技术标签:
【中文标题】无锁的原子函数改变两个独立的内存位置【英文标题】:Atomic function without locks to change two independent memory locations 【发布时间】:2017-05-01 09:02:28 【问题描述】:我有一个名为updateEntry
的函数,它将一个值写入查找表。我想创建这个函数的多线程版本。
我正在研究原子操作__sync_bool_compare_and_swap
,但我不确定如何在此处正确应用它。
理论上是否可以在不锁定的情况下自动实现此功能,因为它会更改两个独立的内存位置entryLookup[id]
和entry
?
void updateEntry(Entry ** entryLookup, unsigned int id, int val1, short val2)
Entry * entry = entryLookup[id];
entry->val1 = val1;
entry->val2 = val2;
entryLookup[id] += sizeof(Entry);
【问题讨论】:
问题:1) 你能确保每个线程都能访问不相交的id
集合吗? 2) 你能保证修改val1
和val2
不会影响entryLookup
operator[]
结果吗? 3) 你能确保修改entryLookup
的值不会影响任何并行线程(例如,新的id 不会被任何其他线程访问)?如果其中任何一条规则不匹配,我认为实现无锁并发会很困难。
@AdrianMaire 非常感谢您的回答。我构建了一个使用不相交 id 集的解决方案,但生成这种不相交集的成本很高。我想避免使用不相交的 ID 集。第 2 点是什么意思。)?
Q4 - 更新是否与查找同步?如果没有,您可以首先自动更新entryLookup[id]
,确保没有其他线程会使用相同的条目,然后在您闲暇时更新值(有点)。如果还要进行查找,则必须同时更新两个单独的位置,而这不能使用原始原子操作来完成。
定义“无锁定”——你总是可以使用原子的“正在更新”标志
我不知道entryLookup
是什么样子:我可以想象一个类,其中operator[]
被重载并访问val1
。想象一个第一个线程,访问 id=5,它返回 0x42 处的条目,在修改 val1 之后,entryLookup 现在对应于(例如)id=10。下一个线程访问 id=10 也对应于 0x42 处的条目。现在两个线程在没有锁的情况下同时访问同一个对象。
【参考方案1】:
为了使这个线程安全,您可以先增加entryLookup[id]
以确保稍后出现的任何其他线程不能更改相同的条目,然后填写值。返回旧值的地方需要原子加法:
void updateEntry(Entry ** entryLookup, unsigned int id, int val1, short val2)
Entry * entry = __sync_fetch_and_add(&entryLookup[id], sizeof(Entry));
entry->val1 = val1;
entry->val2 = val2;
【讨论】:
我刚刚注意到@eran 已经在 cmets 中给出了这个答案。 这很简单,应该可以解决我的问题。非常感谢这个很棒的答案。 @peppincsoda 这里的问题是两个val
条目的修改从未发布(同步)。如何在另一个线程中安全地访问entry
?
请注意,仅当更新阶段在执行查找之前结束时,这才是安全的。否则,查找可能会在设置其值之前看到一个条目。此外,正如@LWimsey 评论的那样,更新结束时应该存在一些写入障碍(即使没有明确请求刷新,这种代码也可能会起作用,但访问这些值仍将被视为数据竞争)。
你说得对,我假设提问者不想在从多个线程更新结构时进行查找。以上是关于无锁的原子函数改变两个独立的内存位置的主要内容,如果未能解决你的问题,请参考以下文章
我们可以用两个或多个无锁容器原子地做一些事情而不锁定两者吗?
Redis学习笔记29——无锁的原子操作:Redis如何应对并发访问
Redis学习笔记29——无锁的原子操作:Redis如何应对并发访问
JMMsynchronized原理可见性有序性happens-beforeCAS(无锁并发)原子性 synchronized的优化
JMMsynchronized原理可见性有序性happens-beforeCAS(无锁并发)原子性 synchronized的优化