我正在寻找可组合操作- 使用事务内存相当容易。(感谢阿米·塔沃里)
使用锁(互斥锁/自旋锁)很容易做到——但它会导致死锁——因此基于锁的算法只能通过手动调整来组合。
无锁算法不存在死锁问题,但它不可组合。需要将 2 个或更多容器设计为单个组合的无锁数据结构。
是否有任何方法、辅助实现或一些无锁算法 - 以原子方式使用多个无锁容器以保持一致性?
- 检查一个项目是否同时在两个容器中
- 以原子方式将元素从一个容器移动到另一个容器
...
或者 RCU 或危险指针可以帮助做到这一点吗?
众所周知,我们可以使用无锁容器,这在其实现中很困难,例如来自并发数据结构 (CDS) 库: http: //libcds.sourceforge.net/doc/cds-api/group__cds__nonintrusive__map.html
例如,我们可以使用像SkipList CDS-lib这样的无锁有序映射
但即使是简单的无锁算法在任何情况下都不是无锁的:
- 迭代器 文档链接
您只能在 RCU lock 下迭代跳过列表集项目。只有在这种情况下,迭代器是线程安全的,因为当 RCU 被锁定时,任何集合的项目都不能被回收。迭代期间对 RCU 锁定的要求意味着元素的删除(即擦除)是不可能的。
::contains(K const &key)
-文档链接
该函数在内部应用 RCU 锁。
- 要
::get(K const &key)
更新我们得到的元素,我们应该使用 lock:documentation-link
例子:
typedef cds::container::SkipListMap< cds::urcu::gc< cds::urcu::general_buffered<> >, int, foo, my_traits > skip_list;
skip_list theList;
// ...
typename skip_list::raw_ptr pVal;
{
// Lock RCU
skip_list::rcu_lock lock;
pVal = theList.get( 5 );
if ( pVal ) {
// Deal with pVal
//...
}
}
// You can manually release pVal after RCU-locked section
pVal.release();
但是如果我们使用 2 个无锁容器而不是 1 个,并且如果我们只使用总是无锁的方法,或者其中一个无锁的方法,那么我们可以在不锁定两个容器的情况下做到这一点吗?
typedef cds::urcu::gc< cds::urcu::general_buffered<> > rcu_gpb;
cds::container::SkipListMap< rcu_gpb, int, int > map_1;
cds::container::SkipListMap< rcu_gpb, int, int > map_2;
我们可以map_1
在map_2
不锁定两个容器的情况下原子地移动 1 个元素- 即map_1.erase(K const &key)
,map_2.insert(K const &key, V const &val)
如果我们想保持原子性和一致性:
其他线程没有看到第一个容器中没有元素,他仍然没有出现在第二个容器中
其他线程看不到第一个容器中有元素,而第二个容器中已经存在相同的元素
如果我们想保持原子性和一致性,我们可以用两个或多个无锁容器原子地做一些事情而不锁定两者吗?
回答:我们不能通过简单地使用它的常用函数来一次对两个或多个无锁容器进行任何原子操作而无需锁。
仅当我们在容器 API 中执行无锁算法提供的 1 次简单操作时,对于 2 个无锁容器来说,1 个锁就足够了,排除上述 3 种情况,即使在无锁容器中也使用锁。
如果您对无锁算法进行了复杂的自定义改进,那么“但可能会有一些额外的开销”,那么您可以提供一些可组合的,例如,“两个队列彼此了解,并且查看它们的代码是精心设计”,正如 Peter Cordes 所说。