here

advertisement
SkipLists and Balanced
Search
The Art Of MultiProcessor Programming
Maurice Herlihy & Nir Shavit
Chapter 14
Avi Kozokin
Intro
• Introduction: SkipList – what, why & how. a reminder
• LazySkipList – a lock based concurrent SkipList
• LockFreeSkipList – lock free concurrent SkipList
SkipList – what
• Collection of unique keys
• Comprised of levels – each level is a sorted linked list.
• Level - O contains all nodes, and each level is a subset of the lower levels.
• Mimics a balanced search tree. Height is logarithmic.
• Each link at level i skips roughly 2 nodes.
• Probabilistic data structure.
• We assume that contains()
i
is called more than the rest
of the methods
SkipList – what
• Collection of unique keys
• Comprised of levels – each level is a sorted linked list.
• Level - O contains all nodes, and each level is a subset of the lower levels.
• Mimics a balanced search tree. Height is logarithmic.
• Each link at level i skips roughly 2 nodes.
• Probabilistic data structure.
• We assume that contains()
i
is called more than the rest
of the methods
https://en.wikipedia.org/wiki/Skip_list
SkipList – why
• Balanced search trees, (such as AVL trees) provide logarithmic performance
of search, add & remove methods.
• The problem is the maintenance – need to rebalance the data structure
after each change (rolling in AVL).
• SkipList provides a solution: uses randomization to achieve logarithmic
performance in average. Randomization happens on insertion of an element
• We choose: 0<p<1 to be the probability for a node to appear in level i given
that it appears in level i-1. all nodes appear in level 0. this means the
probability of a node to appear in level i is p i . Note that if it appears in
level i – it appears in all lower levels.
SkipList – how
• Every Node has a topLevel – index of the highest list in which it is present.
• Every Node has an array of references to nodes – next node on each level.
• SkipList has head and tail nodes, with keys smaller and bigger than any
possible key, respectively.
• head and tail appear on every level, and initially head’s next points to tail.
• SkipList has a maximal level. (can be dynamically maintained).
• Finding nodes by traversing every list starting from the top, and descending
whenever we find the largest key in the level which is not bigger than the
searched node’s key.
Find()
• Traverses the list
• Fills 2 arrays: preds[] and sucss[]
• pred[i] is a reference to the node in level i that has the biggest key that is
smaller than the searched key.
• succ[i] is pred[i]’s successor
• Returns the node’s top level if found
• If not found – returns -1
Find()
• Traverses the list
• Fills 2 arrays: preds[] and sucss[]
• pred[i] is a reference to the
node in level i that has the biggest
key that is smaller than the
searched key.
• succ[i] is pred[i]’s successor
Add()
• Uses find() to fill the preds[] and succs[].
• Generates a random topLevel.
• If find() returned a top level – returns false (the key is already in the list)
• If not found - inserts the node between preds[i] and succs[i] in every level
from 0 to topLevel
Add() - example
• Uses find() to fill the preds[] and succs[].
• Generates a random topLevel.
• If find() returned a top level – returns false (the key is already in the list)
• If not found - inserts the node between preds[i] and succs[i] in every level
from 0 to topLevel
LazySkipList
LazySkipList
• A lock based concurrent SkipList
• Each level of the SkipList is a LazyList
• Add() & remove() use optimistic fine grained locking
• Contains() is wait-free
LazyList – a reminder
• Every node has a marker – if set, that means that the node was logically
removed from the set.
• Add() & remove() methods traverse the list without locking, searching for
the node’s predecessor and successor (the node itself incase of remove)
• After finding the predecessor and successor, locking them
• Validating – both pred and curr aren’t marked, and preds next is in fact
curr.
• If validation succeeds – continue with method.
• If validation failed – go again.
LazySkipList
• Maintains the SkipList property – each level is a subset of it’s lower levels
• Using locks near the modified node to achieve this property.
• Every node has an additional indicator if the node is fullyLinked – meaning
that the node is linked in every level it should be linked (up to it’s top level)
• We delay access to a node until it is fullyLinked
LazySkipList
LazySkipList
Add()
• Calls find() to fill preds[] and succs[] (find is the same as sequential find)
• Locks preds[]
• Checks if the node is already in the set (spins if not fully linked) and returns
false if so.
• Validates preds[]. If validation fails – tries again.
• Makes preds[] point to our node and the node to point to the sucss[]
• Sets fullyLinked
example
Find()
Keeps pred’s key
strictly smaller than
searched key
Find()
Find()
If the node is found
And unmarked we wait until it’s
Fully linked and then finish
Remove()
• Uses find as well
• Locks the node, and checks if it is unmarked, and if the node is fully linked
and at it’s top level (if not – not yet fully linked or marked and is being
removed)
• Locks preds[] and validates. If fails – tries again.
• Removes from top to bottom to maintain the SkipList property
Contains()
• Wait free
• Uses find()
• Validates result
Lock-Free Concurrent SkipList
Lock-Free Concurrent SkipList
• Also based on a concurrent list implementation – LockFreeList
• Each level of the SkipList is a LockFreeList
• No locks in this implementation
• Only CAS operations
• We will achieve a wait free contains() here as well
LockFreeList
• Every node’s next reference is an AtomicMarkableReference
• CompareAndSet(expected_val,new_val,expected_mark,new_mark)
• Get(mark_holder[] )– returns the reference and puts mark in mark_holder
• attemptMark(val,mark) – if references value == val then mark.
• Essentially, using CASes to mark and remove: one CAS to check if the node
still points to it’s successor and a second CAS to physically remove it.
• Find() cleans up marked nodes
LockFreeSkipList - Overview
• We cannot maintain the SkipList property this time.
• The set is defined by the bottom level – if a node is there and unmarked it’s
in the set (no need for fullyLinked flag)
• Adding by using CAS to insert a node at each level.
• Removing by marking the node’s next reference.
• Find() fills preds[] cleans up marked nodes while traversing.
Find(x) & contains(x)
• Traverses the lists, from top level to bottom. Never traverses marked nodes.
• filling preds[] with references to nodes with largest key that’s strictly
smaller than x, while using CAS to eliminate marked nodes.
• Contains() does the same except it doesn’t eliminate marked nodes but
simply skips over them.
Add()
• Uses find() to check presence and to fill preds[] and sucss[]
• Directs new node’s nexts to sucssessors
• Tried to add to bottom level using CAS. If it fails – calls find again.
• Adds to higher levels
Add()
Add()
Add()
Add()
Remove()
• Also uses find(), searching for an unmarked node in the bottom level
• marks the node, starting from it’s top level
• If all marks were successful up to the bottom level, marks the bottom level
• Physically removed by remove itself or find() of other threads
Summary
• We recalled what is a SkipList
• Seems weird , but provides an elegant alternative to a search tree
• Seen 2 different concurrent SkipLists
• One uses locks
• The other doesn’t
• Both achieve logarithmic performance and a wait free contains() method
Thanks!
Download