libslack(list) - list module
#include <slack/list.h>
typedef struct List List; typedef struct Lister Lister; typedef void list_release_t(void *item); typedef void *list_copy_t(const void *item); typedef int list_cmp_t(const void *a, const void *b); typedef void list_action_t(void *item, size_t *index, void *data); typedef void *list_map_t(void *item, size_t *index, void *data); typedef int list_query_t(void *item, size_t *index, void *data);
List *list_create(list_release_t *destroy); List *list_make(list_release_t *destroy, ...); List *list_vmake(list_release_t *destroy, va_list args); List *list_copy(const List *src, list_copy_t *copy); List *list_create_locked(Locker *locker, list_release_t *destroy); List *list_make_locked(Locker *locker, list_release_t *destroy, ...); List *list_vmake_locked(Locker *locker, list_release_t *destroy, va_list args); List *list_copy_locked(Locker *locker, const List *src, list_copy_t *copy); void list_release(List *list); void *list_destroy(List **list); int list_own(List *list, list_release_t *destroy); list_release_t *list_disown(List *list); void *list_item(const List *list, size_t index); int list_item_int(const List *list, size_t index); int list_empty(const List *list); size_t list_length(const List *list); ssize_t list_last(const List *list); List *list_remove(List *list, size_t index); List *list_remove_range(List *list, size_t index, size_t range); List *list_insert(List *list, size_t index, void *item); List *list_insert_int(List *list, size_t index, int item); List *list_insert_list(List *list, size_t index, const List *src, list_copy_t *copy); List *list_append(List *list, void *item); List *list_append_int(List *list, int item); List *list_append_list(List *list, const List *src, list_copy_t *copy); List *list_prepend(List *list, void *item); List *list_prepend_int(List *list, int item); List *list_prepend_list(List *list, const List *src, list_copy_t *copy); List *list_replace(List *list, size_t index, size_t range, void *item); List *list_replace_int(List *list, size_t index, size_t range, int item); List *list_replace_list(List *list, size_t index, size_t range, const List *src, list_copy_t *copy); List *list_extract(const List *list, size_t index, size_t range, list_copy_t *copy); List *list_push(List *list, void *item); List *list_push_int(List *list, int item); void *list_pop(List *list); int list_pop_int(List *list); void *list_shift(List *list); int list_shift_int(List *list); List *list_unshift(List *list, void *item); List *list_unshift_int(List *list, int item); List *list_splice(List *list, size_t index, size_t range, list_copy_t *copy); List *list_sort(List *list, list_cmp_t *cmp); void list_apply(List *list, list_action_t *action, void *data); List *list_map(List *list, list_release_t *destroy, list_map_t *map, void *data); List *list_grep(List *list, list_query_t *grep, void *data); ssize_t list_ask(List *list, ssize_t *index, list_query_t *query, void *data); Lister *lister_create(List *list); void lister_release(Lister * lister); void *lister_destroy(Lister **lister); int lister_has_next(Lister *lister); void *lister_next(Lister *lister); int lister_next_int(Lister *lister); void lister_remove(Lister *lister); int list_has_next(List *list); void list_break(List *list); void *list_next(List *list); int list_next_int(List *list); void list_remove_current(List *list);
This module provides functions for manipulating and iterating over lists of
homogeneous data (or heterogeneous data if it's polymorphic). Lists may own their items. Lists created with a non-NULL
destroy function use that function to destroy an item when it is removed
from the list and to destroy each item when the list itself is destroyed.
Be careful not to insert items owned by one list into a list that doesn't
own its own items unless you know that the source list (and all of the
shared items) will outlive the destination list.
List *list_create(list_release_t *destroy)
Creates a List with destroy
as its item destructor. On success, returns the new list. On error, returns NULL
.
List *list_make(list_release_t *destroy, ...)
Creates a List with destroy
as its item destructor and the remaining arguments as its initial items.
Multiple threads accessing this map will be synchronised by locker
. On success, returns the new list. On error, return NULL
.
List *list_vmake(list_release_t *destroy, va_list args)
Equivalent to list_make() with the variable argument list specified directly as for vprintf(3).
List *list_copy(const List *src, list_copy_t *copy)
Creates a clone of src
using copy
as the copy constructor (if not
NULL
). On success, returns the clone. On error, returns NULL
.
List *list_create_locked(Locker locker, list_release_t *destroy)
Creates a List with destroy
as its item destructor. Multiple threads accessing this list will be
synchronised by locker
. On success, returns the new list. On error, returns NULL
.
List *list_make_locked(Locker *locker, list_release_t *destroy, ...)
Creates a List with destroy
as its item destructor and the remaining arguments as its initial items.
Multiple threads accessing this list will be synchronised by locker
. On success, returns the new list. On error, return NULL
.
List *list_vmake_locked(Locker *locker, list_release_t *destroy, va_list args)
Equivalent to list_make_locked() with the variable argument list specified directly as for vprintf(3).
List *list_copy_locked(Locker *locker, const List *src, list_copy_t *copy)
Creates a clone of src
using copy
as the copy constructor (if not
NULL
). Multiple threads accessing this list will be synchronised by
locker
. On success, returns the clone. On error, returns NULL
.
void list_release(List *list)
Releases (deallocates) list
, destroying its items if necessary.
void *list_destroy(List **list)
Destroys (deallocates and sets to NULL
) *list
. Returns NULL
.
Note: lists shared by multiple threads must not be destroyed until after the
threads have finished with it.
int list_own(List *list, list_release_t *destroy)
Causes list
to take ownership of its items. The items will be destroyed using destroy
when they are removed or when list
is destroyed. On success, returns 0. On error, returns -1.
list_release_t *list_disown(List *list)
Causes list
to relinquish ownership of its items. The items will not be destroyed when
they are removed from list
or when list
is destroyed. On success, returns the previous destroy function, if any. On
error, returns
NULL
.
void *list_item(const List *list, size_t index)
Returns the index
'th item in list
. On error, returns NULL
.
int list_item_int(const List *list, size_t index)
Returns the index
'th item in list
as an integer. On error, returns 0.
int list_empty(const List *list)
Returns whether or not list
is empty.
size_t list_length(const List *list)
Returns the length of list
.
ssize_t list_last(const List *list)
Returns the index of the last item in list
, or -1 if there are no items.
List *list_remove(List *list, size_t index)
Removes the index
'th item from list
. On success, returns list
. On error, returns NULL
.
List *list_remove_range(List *list, size_t index, size_t range)
Removes range
items from list
starting at index
. On success, returns
list
. On error, returns NULL
.
List *list_insert(List *list, size_t index, void *item)
Adds item
to list
at position index
. On success, returns list
. On error, returns NULL
.
List *list_insert_int(List *list, size_t index, int item)
Adds item
to list
at position index
. On success, returns list
. On error, returns NULL
.
List *list_insert_list(List *list, size_t index, const List *src, list_copy_t *copy)
Inserts the items from src
into list
, starting at position index
using copy
as the copy constructor (if not NULL
). On success, returns
list
. On error, returns NULL
.
List *list_append(List *list, void *item)
Appends item
to list
. On success, returns list
. On error, returns
NULL
.
List *list_append_int(List *list, int item)
Appends item
to list
. On success, returns list
. On error, returns
NULL
.
List *list_append_list(List *list, const List *src, list_copy_t *copy)
Appends the items in src
to list
using copy
as the copy constructor (if not NULL
). On success, returns list
. On error, returns NULL
.
List *list_prepend(List *list, void *item)
Prepends item
to list
. On success, returns list
. On error, returns
NULL
.
List *list_prepend_int(List *list, int item)
Prepends item
to list
. On success, returns list
. On error, returns
NULL
.
List *list_prepend_list(List *list, const List *src, list_copy_t *copy)
Prepends the items in src
to list
using copy
as the copy constructor (if not NULL
). On success, returns list
. On error, returns NULL
.
List *list_replace(List *list, size_t index, size_t range, void *item)
Replaces range
items in list
, starting at index
, with item
. On success, returns list
. On error, returns NULL
.
List *list_replace_int(List *list, size_t index, size_t range, int item)
Replaces range
items in list
, starting at index
, with item
. On success, returns list
. On error, returns NULL
.
List *list_replace_list(List *list, size_t index, size_t range, const List *src, list_copy_t *copy)
Replaces range
items in list
, starting at index
, with the items in
src
using copy
as the copy constructor (if not NULL
). On success, return list
. On error, returns NULL
.
List *list_extract(const List *list, size_t index, size_t range, list_copy_t *copy)
Creates a new list consisting of range
items from list
, starting at
index
, using copy
as the copy constructor (if not NULL
). On success, returns the new list. On error, returns NULL
.
List *list_push(List *list, void *item)
Pushes item
onto the end of list
. On success, returns list
. Om error, returns NULL
.
List *list_push_int(List *list, int item)
Pushes item
onto the end of list
. On success, returns list
. Om error, returns NULL
.
void *list_pop(List *list)
Pops the last item off list
. On success, returns the item popped. On error, returns NULL
.
int list_pop_int(List *list)
Pops the last item off list
. On success, returns the item popped. On error, returns 0
.
void *list_shift(List *list)
Removes and returns the first item in list
. On success, returns the item shifted. On error, returns NULL
.
int list_shift_int(List *list)
Removes and returns the first item in list
. On success, returns the item shifted. On error, returns 0
.
List *list_unshift(List *list, void *item)
Inserts item
at the start of list
. On success, returns list
. On error, returns NULL
.
List *list_unshift_int(List *list, int item)
Inserts item
at the start of list
. On success, returns list
. On error, returns NULL
.
List *list_splice(List *list, size_t index, size_t range)
Removes a sublist from list
starting at index
of length range
items. On success, returns the sublist. It is the caller's responsibility
to deallocate the new sublist with list_release() or list_destroy(). On error, returns NULL
.
List *list_sort(List *list, list_cmp_t *cmp)
Sorts the items in list
using the item comparison function cmp
and
qsort(3) for lists of fewer than 10000 items and hsort(3) for larger lists. On success, returns list
. On error, returns NULL
.
void list_apply(List *list, list_action_t *action)
Invokes action
for each of list
's items. The arguments passed to
action
are the item, a pointer to the loop variable containing the item's position
within list
and data
.
List *list_map(List *list, list_release_t *destroy, list_map_t *map, void *data)
Returns a new list containing the return values of map
, invoked once for each item in list
. The arguments passed to map
are the item, a pointer to the loop variable containing the item's position
within list
and
data
. destroy
will be the destroy function for the returned list. The user is responsible
for deallocating the returned list with
list_destroy() or list_release(). On success, returns the new list. On error, returns NULL
.
List *list_grep(List *list, list_query_t *grep, void *data)
Invokes grep
once for each item in list
, building a new list containing the items that resulted in grep
returning a non-zero value. Returns this new list. The arguments passed to grep
are the item, a pointer to the loop variable containing the item's position
within list
and data
. The user is responsible for deallocating the returned list with
list_destroy() or list_release(). Note that the list returned does not own its items since it does not copy
the items. On success, returns the new list. On error, returns NULL
.
ssize_t list_ask(List *list, ssize_t *index, list_query_t *query, void *data)
Invokes query
on each item of list
, starting at index
until
query
returns a non-zero value. The arguments passed to query
are the item, index
and data
. Returns the index of the item that satisfied query, or -1 when query is
not satisfied by any remaining items. The value pointed to by index
is set to the return value.
Lister *lister_create(List *list)
Creates an iterator for list
. On success, returns the iterator. On error, returns NULL
.
void lister_release(Lister *lister)
Releases (deallocates) lister
.
void *lister_destroy(Lister **lister)
Destroys (deallocates and sets to NULL
) *lister
. Returns NULL
.
Note: listers shared by multiple threads must not be destroyed until after the
threads have finished with it.
int lister_has_next(Lister *lister)
Returns whether or not there is another item in the list being iterated
over by lister
.
void *lister_next(Lister *lister)
Returns the next item in the iteration lister
. On error, returns NULL
.
int lister_next_int(Lister *lister)
Returns the next item in the iteration lister
as an integer. On error, returns -1
.
void lister_remove(Lister *lister)
Removes the current item in the iteration lister
. The next item in the iteration is the item following the removed item, if
any. This must be called after lister_next() or lister_next_int().
int list_has_next(List *list)
Returns whether or not there is another item in list
. The first time this is called, a new, internal Lister will be created (Note: There can be only one). When there are no more
items, returns zero and destroys the internal iterator. When it returns a
non-zero value, use list_next() to retrieve the next item.
Note: If an iteration using an internal iterator terminates before the end of the list, it is the caller's responsibility to call list_break(). Failure to do so will cause the internal iterator to leak which will leave the list write locked. The next use of list_has_next() for the same list will not do what you expect. In fact, the next attempt to use the list would deadlock the prgoram.
void list_break(List *list)
Unlocks list
and destroys its internal iterator. Must be used only when an iteration
using an internal iterator has terminated before reaching the end of list
.
void *list_next(List *list)
Returns the next item in list
using it's internal iterator. On error, returns NULL
.
int list_next_int(List *list)
Returns the next item in list
as an integer using it's internal iterator. On error, returns -1
.
void list_remove_current(List *list)
Removes the current item in list
using it's internal iterator. The next item in the iteration is the item
following the removed item, if any. This must be called after list_next().
MT-Disciplined
By default, Lists are not MT-Safe because most programs are single threaded and synchronisation doesn't come for free. Even in multi threaded programs, not all Lists are necessarily shared between multiple threads.
When a List is shared between multiple threads which need to be synchronised, the method of synchronisation must be carefully selected by the client code. There are tradeoffs between concurrency and overhead. The greater the concurrency, the greater the overhead. More locks give greater concurrency but have greater overhead. Readers/Writer locks can give greater concurrency than Mutex locks but have greater overhead. One lock for each List may be required, or one lock for all (or a set of) Lists may be more appropriate.
Generally, the best synchronisation strategy for a given application can only be determined by testing/benchmarking the written application. It is important to be able to experiment with the synchronisation strategy at this stage of development without pain.
To facilitate this, Lists can be created with list_create_locked() which takes a Locker argument. The Locker specifies a lock and a set of functions for manipulating the lock. Each List can have it's own lock by creating a separate Locker for each List. Multiple Lists can share the same lock by sharing the same Locker. Only the application developer can determine what is appropriate for each application on a case by case basis.
MT-Disciplined means that the application developer has a mechanism for specifying the synchronisation requirements to be applied to library code.
Little attempt is made to protect the client from sharing items between lists with differing ownership policies and getting it wrong. When copying items from any list to an owning list, a copy function must be supplied. When adding a single item to an owning list, it is assumed that the list may take over ownership of the item. When an owning list is destroyed, all of its items are destroyed. If any of these items had been shared with a non-owning list that outlived the owning list, then the non-owning list will contain items that point to deallocated memory.
If you use an internal iterator in a loop that terminates before the end of the list, and fail to call list_break(), the internal iterator will leak and the list will remain write locked, deadlocking the program the time you attempt to access the list.
Uses malloc(3). Need to decouple memory type and allocation strategy from this code.
libslack(3), map(3), mem(3), hsort(3), qsort(3), thread(3)
20010215 raf <raf@raf.org>