The transactional queue provides a transaction-safe implementation of a single-ended FIFO queue. Efficient insert operations are supported on one end, efficient remove operations are supported on the opposite end. More...
Files | |
file | picotm-txqueue-state.h |
Provides non-transactional state and entries for transactional queues. | |
file | picotm-txqueue.h |
Provides transactional queues. | |
Queue entries are represented by struct txqueue_entry
. We can add an instance of this data structure to any data value to turn it into a queue entry. Here's an example for queues of values of type unsigned long
.
This code initializes the queue entry using txqueue_entry_init()
. The macro TXQUEUE_ENTRY_INITIALIZER
initializes static or stack-allocated queue entries. The example below illustrates this.
When both, macro and function initializers, are possible, the macro form is prefered. Queue entries are uninitialized with txqueue_entry_uninit()
.
To store the queue entries, we need non-transactional queue state, which represents the shared state of a queue. It's implemented by struct txqueue_state
. We can define and initialize a queue state as illustrated in the example below.
This code uses the initializer function txqueue_state_init()
. For static or stack-allocated queue states, there's the initializer macro TXQUEUE_STATE_INITIALIZER()
.
When both forms are possible, the initializer macro is prefered.
Queue-state clean-up is performed by txqueue_state_uninit()
. The queue state may not contain entries when the clean-up happens. This means that entries have to be cleaned up from within a transaction.
For many uses, this requirement just imposes unnecessary overhead. A call to txqueue_state_clear_and_uninit_entries()
provies a non-transactional way of clearing the queue from its entries. It erases each element from the queue and calls a clean-up function on it. The example below shows the clean-up code for a queue of struct ulong_item
entries.
At this point we have a queue state and queue entries to add to the state. So far all code was non-transactional. Actual queue access and manipulation is performed by transactional code.
To perform queue operations within a transaction, we first need a queue data structure for our transaction. It's represented by struct txqueue
. A call to txqueue_of_state_tx()
returns an instance.
Calling txqueue_of_state_tx()
multiple times for the same queue state within the same transaction returns the same queue. Queues are undefined after their transaction committed or aborted, or within other, concurrent transactions.
With the queue, we can now append entries to the queue state using txqueue_push_tx()
. The example below illustrates this.
After this transaction committed, the ulong data item item
will be the final entry in queue_state
. If the transactions has to abort after the call to txqueue_push_tx()
, the transaction framework will automatically remove the appended entry during the rollback; thus restoring the original state.
To remove the next entry from the queue, we can call txqueue_pop_tx()
. This call is often combined with txqueue_front_tx()
, which returns the queue's front-end entry. The combination is illustated in the example below.
As usual, all errors are detected and handled by the transaction framework. The benefits of transactional code show when we move entries between queues.
In this example, we remove an entry from a source source and append it to a destination queue. The transaction framework automatically isolates these operations from concurrent transactions until the transaction commits. So concurrent transactions see the entry in either the source queue or the destination queue, but never in both. If the transaction has to roll back after the append operation, the transaction framework automatically removes the queue entry from the destination queue and returns it to its old position in the source queue.
A call to txqueue_size_tx()
returns the number of queue entries, a call to txqueue_empty_tx()
returns true
if a queue is empty. The former function might have linear complexity, the later function always has constant complexity. It's therefore better to use txqueue_empty_tx()
if it's only relevant whether there are entries.