I'm looking for Composable operations - it fairly easily to do using transactional memory. (Thanks to Ami Tavory)
And it easily to do using locks (mutex/spinlock) - but it can lead to deadlocks - so lock-based algorithms composable only with manual tuning.
Lock-free algorithms do not have the problem of deadlocks, but it is not composable. Required to designed 2 or more containers as a single composed lock-free data structure.
Is there any approach, helper-implementation or some lock-free algorithms - to atomically work with several lock-free containers to maintain consistency?
- To check if if an item is in both containers at once
- To move element from one container to another atomically
...
Or can RCU or hazard-pointers help to do this?
As known, we can use lock-free containers, which is difficult in its implementations, for example from Concurrent Data Structures (CDS) library: http://libcds.sourceforge.net/doc/cds-api/group__cds__nonintrusive__map.html
And for example we can use lock-free ordered-map like SkipList CDS-lib
But even simple lock-free algorithm is not lock-free for any cases:
- Iterators documentation-link
You may iterate over skip-list set items only under RCU lock. Only in this case the iterator is thread-safe since while RCU is locked any set's item cannot be reclaimed. The requirement of RCU lock during iterating means that deletion of the elements (i.e. erase) is not possible.
::contains(K const &key)
- documentation-link
The function applies RCU lock internally.
- To
::get(K const &key)
and update element which we got, we should use lock: documentation-link
Example:
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();
But if we use 2 lock-free containers instead of 1, and if we use only methods wich is always lock-free, or one of it lock-free, then can we do it without locking both containers?
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;
Can we atomically move 1 element from map_1
to map_2
without locking both containers - i.e. map_1.erase(K const &key)
and map_2.insert(K const &key, V const &val)
if we want to maintain atomicity and consistency:
that other threads do not see that there is no element in the first container, and he still had not appear in the second
that other threads do not see that there is element in the first container, and the same element already in the second
Can we do something atomically with 2 or more lock-free containers without locking both - if we want to maintain atomicity and consistency?
ANSWER: We can't do any atomically operations with two or more lock-free containers at once without locks by using simply its usual functions.
Only if we do 1 simply operation provided by lock-free algorithm in containers-API then for 2 lock-free containers it is enough 1 lock, exclude 3 cases described above when even in lock-free containers uses locks.
Also "but maybe something with a bunch of extra overhead" if you made complicated custom improvements of lock-free algorithms then you can provide some composable, for example, as "the two queues know about each other, and the code looking at them is carefully designed" as Peter Cordes noted.