picotm
0.8.0
|
The transactional multiset provides a transaction-safe implementation of a multiset. Multisets are sorted sets of elements. Duplicate entries are supported. More...
Files | |
file | picotm-txmultiset-state.h |
Provides non-transactional state and entries for transactional multisets. | |
file | picotm-txmultiset.h |
Provides transactional multisets. | |
The struct txmultiset
data structure represents a transactional multiset (i.e., a set that can hold the same value multiple times). To create a multiset, we first need multiset entries. These are represented by struct txmultiset_entry
. We can add an instance of this data structure to any data value to turn it into a multiset entry. Here's an example for multisets of values of type unsigned long
.
This code initializes the multiset entry using txmultiset_entry_init()
. The macro TXMULTISET_ENTRY_INITIALIZER
initializes static or stack-allocated multiset entries. The example below illustrates this.
When both, macro and function initialization, is possible, the macro form is prefered. Multiset entries are uninitialized with txmultiset_entry_uninit()
.
To store the multiset entries, we need a non-transactional multiset state, which represents the shared state of a multiset. It's implemented by struct txmultiset_state
. We can define and initialize a multiset state as illustrated in the example below.
The initializer function takes the multiset state and two additional call-back functions, which are required for sorting the multiset's entries. The key_cb()
call-back function returns a pointer to the key for a given entry. In the example above, it's the value itself. The compare_cb()
call-back function compares two keys. It returns a value less than, equal to, or greater than zero if the left-hand-side value is less than, equal to, or greater than the right-hand-side value. Entries in a multiset are sorted by their key in ascending order. These call-back functions provide the key and the compare operation.
The code uses the initializer function txmultiset_state_init()
. For static or stack-allocated multiset states, there's the initializer macro TXMULTISET_STATE_INITIALIZER()
.
When both forms are possible, the initializer macro is prefered.
Multiset-state clean-up is performed by txmultiset_state_uninit()
. The multiset state may not contain entries when the clean-up happens. This means that entries have to be removed from within a transaction.
For many uses this requirement just imposes unnecessary overhead. A call to txmultiset_state_clear_and_uninit_entries()
provies a non-transactional way of clearing the multiset from its entries. It erases each element from the multiset and calls a clean-up function on it. The example below shows the clean-up code for a multiset of struct ulong_item
entries.
At this point we have a multiset state and multiset entries to add to the state. So far all code was non-transactional. Actual multiset access and manipulation is performed by transactional code.
To perform multiset operations within a transaction, we first need a multiset data structure for our transaction. It's represented by struct txmultiset
. A call to txmultiset_of_state_tx()
returns an instance.
Calling txmultiset_of_state_tx()
multiple times for the same multiset state within the same transaction returns the same multiset. Multisets are undefined after their transaction committed or aborted, or within other, concurrent transactions.
With the multiset, we can now insert entries into the multiset state using txmultiset_insert_tx()
. The example below illustrates this.
After this transaction committed, the ulong data item item
will be the in multiset_state
. If the transactions has to abort after the call to txmultiset_insert_tx()
, the transaction framework will automatically remove the entry during the rollback; thus restoring the original state.
The inserted entry will be sorted in ascending order into the multiset, using the key the compare call-back functions. The insert function compares the key of the new entry to the keys of existing entries to determines the new entry's position. The set is implemented as a tree, so inserting requires a compare operation with only a small fraction of existing items.
To remove an entry from the multiset, we can call txmultiset_erase_tx()
, as illustated in the example below.
Removing an entry keeps the entries sorted. The multiset's implementation re-arranges the entries automatically with very little overhead. As usual, all errors are detected and handled by the transaction framework. The benefits of transactional code show when we move entries between multisets.
In this example, we removed an entry from a source multiset and inserted it into a destination multiset. The transaction framework automatically isolates these operations from concurrent transactions until the transaction commits. So concurrent transactions see the entry in either the source multiset or the destination multiset, but never in both. If the transaction has to roll back after the insert operation, the transaction framework automatically removes the multiset entry from the destination multiset and returns it to its old position in the source multiset.
A call to txmultiset_size_tx()
returns the number of multiset entries, a call to txmultiset_empty_tx()
returns true
if a multiset is empty. The former function might have linear complexity, the later function always has constant complexity. It's therefore better to use txmultiset_empty_tx()
if it's only relevant whether there are entries.
We can iterate over the entries of a multiset. The first entry of the multiset is returned by txmultiset_begin_tx()
. The terminator entry after the final entry is returned by txmultiset_end_tx()
. The terminator entry is not a real entry and should not be dereferenced. Calls to txmultiset_entry_next_tx()
and txmultiset_entry_prev_tx()
return an entry's successor or predecessor. Here's an example that iterates over a multiset of values of type unsigned long
and sums up the individual values.
Being a sorted data structure, multisets offer efficient search operations. A call to txmultiset_find_tx()
looks-up an entry by a key. A multiset can contain multiple entries with the same key. In this case txmultiset_find_tx()
returns one of them. The exact entry can vary among calls.
The beginning and end of a range of entries with the same key is be obtained by txmultiset_lower_bound_tx()
and txmultiset_upper_bound_tx()
. The former returns the first entry with the specified key; the latter returns the entry after the final entry with the specified key. By iterating over the range, all entries with the key can be obtained.
The above example counts the number of entries for the given key. The function txmultiset_count_tx()
performs this operation more efficiently.
Finally, to clear the whole multiset at once, there's txmultiset_clear_tx()
. It's equivalent to a continuous erase operation, but prefered for its reduced overhead.