21 Commits
Author SHA1 Message Date
Patrick Pelissier b596e81e88 Regenerate depend 2018-12-14 21:48:21 +01:00
Patrick Pelissier b7bbe15547 Fix missing header in install 2018-12-14 21:46:38 +01:00
Patrick Pelissier cb5f8c20c8 Update analysis 2018-12-11 23:23:09 +01:00
Patrick Pelissier 61a1e15103 Update analysis 2018-12-11 23:19:33 +01:00
Patrick Pelissier 2416c2e466 Fix bug of overflow of 'alloc-num' if alloc < num (case where the service is called with a big number to insert 2018-12-05 17:52:02 +01:00
Patrick Pelissier b898ce3c56 update analysis 2018-11-29 23:24:11 +01:00
Patrick Pelissier 061e83b9e1 Add basic test for CONCURRENT_RP_DEF 2018-11-29 22:18:44 +01:00
Patrick Pelissier 0a8f727908 Remove CONCURRENT_RP2_DEF (Insuficient performance) 2018-11-27 07:47:06 +01:00
Patrick Pelissier ba584ab096 Use M_THREAD_ATTR 2018-11-26 23:26:42 +01:00
Patrick Pelissier 1fb1fa2e0a Add M_THREAD_ATTR 2018-11-26 23:26:08 +01:00
Patrick Pelissier 8cf780a60f Update comment 2018-11-25 22:43:37 +01:00
Patrick Pelissier ae171115bc Export method _size in concurrent if operator GET_SIZE defined. 2018-11-25 18:03:25 +01:00
Patrick Pelissier a8620c3aff Export method _size for operator GET_SIZE 2018-11-25 18:00:47 +01:00
Patrick Pelissier c154cdf46c Add operator GET_SIZE 2018-11-25 17:52:24 +01:00
Patrick Pelissier 77c59e2b85 Fix const issue. 2018-11-25 17:49:36 +01:00
Patrick Pelissier f07656757f Add another candidate for concurrent with parallel reader. 2018-11-25 11:44:28 +01:00
Patrick Pelissier a1c22fb679 Add first alpha version of Concurrent Container with support concurrent reading. 2018-11-24 19:31:31 +01:00
Patrick Pelissier 3640041c47 Port internal functions of concurrent to use the local lock interface. 2018-11-24 12:20:37 +01:00
Patrick Pelissier d3934dccb9 Add missing const in prototypes. 2018-11-24 11:46:28 +01:00
Patrick Pelissier 10cf2f2270 Prepare concurent for using different lock strategy 2018-11-24 11:37:09 +01:00
Patrick Pelissier c180b2b96d Update analysisd 2018-11-22 07:49:15 +01:00
18 changed files with 521 additions and 102 deletions

No files matched your search

+2 -2
View File
@@ -10,11 +10,11 @@ PREFIX=/usr/local
# Package name & version.
PACKAGE=m*lib-$(VERSION)
VERSION=0.2.0
VERSION=0.2.1
# Define the contain of the distribution tarball
# TODO: Get theses lists from GIT itself.
HEADER=m-algo.h m-array.h m-atomic.h m-bitset.h m-bptree.h m-buffer.h m-core.h m-deque.h m-dict.h m-genint.h m-i-list.h m-i-shared.h m-list.h m-mempool.h m-mutex.h m-prioqueue.h m-rbtree.h m-shared.h m-snapshot.h m-string.h m-tuple.h m-variant.h m-worker.h
HEADER=m-algo.h m-array.h m-atomic.h m-bitset.h m-bptree.h m-buffer.h m-concurrent.h m-core.h m-deque.h m-dict.h m-genint.h m-i-list.h m-i-shared.h m-list.h m-mempool.h m-mutex.h m-prioqueue.h m-rbtree.h m-shared.h m-snapshot.h m-string.h m-tuple.h m-variant.h m-worker.h
DOC1=LICENSE README.md
DOC2=doc/API.txt doc/Container.html doc/Container.ods doc/DEV.md doc/ISSUES.org doc/depend.png doc/oplist.png
EXAMPLE=example/ex-array01.c example/ex-array04.c example/ex-dict02.c example/ex-grep01.c example/ex-multi01.c example/ex-rbtree01.c example/ex-array02.c example/ex-buffer01.c example/ex-dict03.c example/ex-list01.c example/ex-multi02.c example/Makefile example/ex-array03.c example/ex-dict01.c example/ex-dict04.c example/ex-mph.c example/ex-multi03.c
+1
View File
@@ -440,6 +440,7 @@ Other documented operators are:
* SET\_KEY (container, key, object): Associate in the container the key 'key' to the object 'object'.
* GET\_SET\_KEY (container, key) --> &obj: return a pointer to the object within the container associated to the key 'key' or create a new object in the container, associate it to the key 'key' and return its pointer. The pointer to the object remains valid until any modification of the container. The returned pointer cannot be NULL.
* ERASE\_KEY (container, key) --> bool: true to erase the object associated to the key 'key' within the container. Return true if successful, false if the key is not found.
* GET\_SIZE (container) --> size_t: return the number of elements in the container.
* PUSH(container, obj) : push 'object' into 'container'. How it is pushed is container dependent.
* POP(&obj, container) : pop an object from 'container' and save it in '*obj' if obj is not NULL (giving back the ownership to the caller). Which object is popped is container dependent. Undefined behavior is there is no object in the container.
* PUSH_MOVE(container, &obj) : push and move the object '*obj' into 'container'. How it is pushed is container dependent but '*obj' is cleared afterward.
+3
View File
@@ -63,6 +63,9 @@ cputime (void)
#endif
unsigned long g_result;
#if defined(MULTI_THREAD_MEASURE)
M_THREAD_ATTR
#endif
unsigned int randValue = 0;
static void rand_init(void)
+150 -5
View File
@@ -59,16 +59,17 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
A ressouce handler which shall associated a unique handle to a ressource.
The handle shall be unique for the ressource and shall not be reused.
It is typically a 64 bits integers always incremented (even the program
It is typically a 64 bits integers always incremented (even if the program
creates one billion ressources per second, the counter won't overflow
until 585 years).
The ressource handler shall also keep count on:
The ressource handler shall make an association between a HANDLE 64 bits and:
- how much real owners claim to own the ressource
(the ressource is only owned by the ressource handler, however
it acts as delegate of the real owner),
it acts as a delegate of the real owner),
- how much users keep a pointer to the ressource.
- pointer to the resource itself.
This may be a better alternative than shared_ptr & weak_ptr:
@@ -77,7 +78,12 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
- shared_ref & weak_ref becomes only HANDLE,
- all ressources can be freed in one pass.
Needs lock free dictionnary.
Needs lock free dictionnary or at least concurrent dictionnary.
How to handle multiple resource ?
* variant: works fine. Memory usage can be (much) higher than needed if there is a lot of dissimilarity between the size of the objects.
* embedded the type in the ressource handler: more work, API more complex. Memory usage seems better.
* TODO #16: Lock Free List :ENHANCEMENT:
@@ -90,7 +96,6 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
- FREE: Can be used for allocation.
- IN_USE: The node is currently used in the list.
- TO_BE_RECLAIMED: The node is semantically removed, but some others may still be used it.
- CAN_BE_RECLAIMED: The node is semantically removed and no one still use it.
So separate lists shall be used for the memory handler.
It shall also be compatible with NUMA and avoid putting all nodes in the same lists if possible:
@@ -98,10 +103,132 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
The ABA problem shall also be taken into account. Normaly with the provided state of the nodes,
it should not be a problem.
backoff should also be considered when using CAS.
Concurrent insertion / insertion and insertion / deletion and deletion / deletion shall be taken into account.
** First step: lock free node pool :
*** API based on Lock-Free Queue List
-- node_t is { node_t *next; uint ticket; data_t }
-- freelist_t : basic internal lockfree list used to construct more user friendly list (FIFO queue to avoid putting constraints on free & alloc at the same times + it is needed for reclaim part).
do not ensure consistensy of data.
is { _Atomic(node*) head, tail; }
Atomic Queue List : See algorithm by Michael & Scott on Lock Free Queue List (aka MSList)
+ freelist_push (&list, &node)
+ freelist_pop(&list) return node
+ freelist_pop_if(&list, ticket) return node Needs minor adaptation on _pop.
-- lf_memory_core_t : basic type responsible for the centralization of the list allocation.
+ init(lf_mc_t &, int max_thread, size_t alloc_size)
+ clear(lf_mc_t &)
+ lc_mc_t is {
atomic_uint ticket;
atomic_uint reg_thread; // ==> genint instead
atomic_uint tab_ticket[MAX_THREAD];
freelist_t free[MAX_THREAD];
freelist_t to_be_reclaimed[MAX_THREAD];
}
-- lf_memory_thread_t: specialization for the current thread
* lf_mt_t is { int thd_idx; lf_mc_t *global;}
* init(lf_mt_t &, &lf_mc_t g) is { thd_idx := atomic_add(&g.reg_tread, 1); .global := g; }
* clear is ??
* awake () is preparing the thread to handle operations on the list { .global.tab_ticket[.thd_idx] := atomic_add(&global.ticket, 1)+1; }
* sleep () is sleeping the thread so that it doesn't handle operations on the list and perform memory reclamation if needed {
.global.tab_ticket[.thd_idx] := UINT_MAX;
min_ticket := min(global.tab_ticket);
scan the list to_be_reclaimed while node.cpt < min_ticket,
pop the node from to_be_reclaimed and push it to the list free
lf_memory_thread_t shall be in awake state to do anything on the list.
*** Bench
Basic malloc / free: 9.6s
LF Node : 10.6s
Even if LF Node ensures a correct life cycle of the node for the read, the performance is passable.
Too much CAS:
51.40 6.13 6.13 51146587 0.00 0.00 lf_freelist_push
20.10 8.53 2.40 28143911 0.00 0.00 lf_freelist_pop
15.65 10.40 1.87 253579 0.01 0.02 lf_mempool_sleep
*** Mixed API
To overcome this limitation performs a mixed approache:
- having a simple (non atomic) list of nodes to alloc quickly the nodes.
- embedded within an atomic list of list of nodes for GC.
Thread data will be:
- FreeList is a non atomic singly linked list.
- ToBeReclaimedList is the same
- FreeListList is an atomic queue list of non atomic singly list.
- CanBeReclaimedListList is an atomic queue list of non atomic singly list
**** alloc() returns node:
- Pop from FreeList a node.
- If failure, atomic pop from FreeListList a list of list of free nodes. If success, update FreeList with the new list and pop a node from it.
- If failure, try an atomic from the FreeListList of other threads. If success, update FreeList with the new list and pop a node from it.
- If failure, malloc a new pool of FreeListList (TBD: all nodes in one pass? Needs a separate list to handle final free of nodes).
*** free(node)
- Push node in ToBeReclaimedList
*** GarbageCollector called on sleep:
- Compute min_ticket for all threads
- Atomic push of ToBeReclaimedList in CanBeReclaimedListList
- for each thread starting from mine:
-- Atomic pop from CanBeReclaimedListList[] if max ticket of pool of nodes < min_ticket and atomic push in FreeListList
max ticket of pool is the ticket of the top node of the list.
==> Should be much faster since there is much less CAS operations performed and GC is perfomed per block.
** Second step: lf_list_t is a stack based list based on RCU:
_read: classic non atomic operations.
_push(list, data, lf_mt_t) is {
allocate new node from lf_mt_t.global.free, or malloc if nothing.
copy data into node
head := atomic_load(&list.head)
do
node->next := head
while !CAS(&list.head, &head, node)
# ABA problem shall not happen since node cannot be reused immediately
_pop(list, data, lf_mt_t) is {
head := atomic_load(&list.head)
do {
if head == NULL return NULL
} while !CAS(&list.head, &head, head.next)
copy head.data into data
head.cpt := atomic_read(&global.ticket)
push node in global.to_be_reclaimed
}
_erase() TODO
_update() TODO
** Example / Way to use:
- init global memory handler.
- for each thread,
-- init thread memory handler XX,
-- ...
-- XX.awake()
-- ...
-- do as many stuff on any list as we want (this is hot spot).
-- ...
-- XX.sleep() # Memory can be reclaimed
-- XX.awake()
* TODO #15: Constructor / Destructor Prologue / Epilogue for Stack Exception Handling :ENHANCEMENT:
@@ -144,6 +271,16 @@ Update:
API transformation support enables "instance-based allocator" to be made easily.
Needs some formal operator in the oplist to support it fully and an example.
Can be supported using another API extension, some more operators and forcing some names:
* API_N: call like FUNC(obj->extra_data, type)
'obj' is a forced named corresponding to an alias to an object in the function.
Operator needed:
- EXTRA_DATA: Add an extra-data field wihtin the container. Defines the type of data.
* TODO #12: Atomic shared pointer :ENHANCEMENT:
@@ -261,3 +398,11 @@ Another solution is to create a huge list of items which is:
Hash table will only give quick access to items to this list. Expanding the table won't change the order of the list (so the items will remain at the same place and always accessible by other threads) but just add sentinel in the sentinel at the right place (there won't be any reallocation of the list). Inserting will need to insert an item at the right place.
Not sure it will really help. To analyse.
Concurrent dictionary is possible now with CONCURRENT_DEF + DICT_DEF.
But it uses a global lock for all dictionary access.
** Open Addressing Indirect Table with a lock free node pool used as transaction pool.
A good way maybe Open Addessing table used only for indirection and a freelist memory reclamation container for handle the entries
(like a transaction).
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 83 KiB

+12 -4
View File
@@ -85,6 +85,7 @@
,GET_KEY(M_C(name, _get)) \
,M_IF_METHOD(INIT, oplist)(GET_SET_KEY(M_C(name, _get_at)) ,) \
,ERASE_KEY(M_C(name, _erase)) \
,GET_SIZE(M_C(name, _size)) \
,PUSH(M_C(name,_push_back)) \
,POP(M_C(name,_pop_back)) \
,PUSH_MOVE(M_C(name,_push_move)) \
@@ -218,7 +219,7 @@
} \
\
static inline void \
M_C(name, _set_at)(array_t v, size_t i, type x) \
M_C(name, _set_at)(array_t v, size_t i, type const x) \
{ \
ARRAYI_CONTRACT(v); \
assert(v->size > 0 && v->ptr != NULL && i < v->size); \
@@ -495,10 +496,17 @@
ARRAYI_CONTRACT(v); \
assert(i <= v->size); \
size_t size = v->size + num; \
/* Avoid overflow of v->size + num */ \
if (v->size > v->alloc-num) { \
/* Test for overflow of variable size */ \
if (M_UNLIKELY (size <= v->size)) { \
/* Unlikely case of nothing to do */ \
if (num == 0) return; \
M_MEMORY_FULL(sizeof (type) * v->size); \
return ; \
} \
/* Test if alloc array is sufficient */ \
if (size > v->alloc) { \
size_t alloc = M_CALL_INC_ALLOC(oplist, size) ; \
if (M_UNLIKELY (size <= v->size || alloc <= v->alloc)) { \
if (M_UNLIKELY (alloc <= v->alloc)) { \
M_MEMORY_FULL(sizeof (type) * alloc); \
return ; \
} \
+1
View File
@@ -689,6 +689,7 @@ bitset_clz(const bitset_t set)
,TYPE(bitset_t) \
,SUBTYPE(bool) \
,TEST_EMPTY(bitset_empty_p), \
,GET_SIZE(bitset_size) \
,IT_TYPE(bitset_it_t) \
,IT_FIRST(bitset_it) \
,IT_SET(bitset_it_set) \
+2 -2
View File
@@ -400,7 +400,7 @@
return n; \
} \
\
static inline value_t *M_C(name, _get)(tree_t b, key_t const key) \
static inline value_t *M_C(name, _get)(const tree_t b, key_t const key) \
{ \
BPTREEI_CONTRACT(N, key_oplist, b); \
pit_t pit; \
@@ -415,7 +415,7 @@
return NULL; \
} \
\
static inline const value_t *M_C(name, _cget)(tree_t b, key_t const key) \
static inline const value_t *M_C(name, _cget)(const tree_t b, key_t const key) \
{ \
return M_CONST_CAST(value_t, M_C(name, _get)(b, key)); \
} \
+2
View File
@@ -581,6 +581,8 @@ M_C(name, _init)(buffer_t v, size_t size) \
,PUSH(M_C(name,_push)) \
,POP(M_C(name,_pop)) \
,OPLIST(oplist) \
,TEST_EMPTY(M_C(name, _empty_p)), \
,GET_SIZE(M_C(name, _size)) \
)
+293 -88
View File
@@ -27,6 +27,7 @@
#include "m-core.h"
#include "m-mutex.h"
#include "m-atomic.h"
/* Define a protected concurrent container and its associated functions.
USAGE: CONCURRENT_DEF(name, type [, oplist_of_the_type]) */
@@ -44,6 +45,12 @@
(__VA_ARGS__ )))
/* Define a protected concurrent container and its associated functions with Read Preference.
USAGE: CONCURRENT_RP_DEF(name, type [, oplist_of_the_type]) */
#define CONCURRENT_RP_DEF(name, ...) \
CONCURRENTI_RP_DEF(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), M_C(name,_t), M_C(name,_it_t) ), \
(name, __VA_ARGS__, M_C(name,_t), M_C(name,_it_t))))
/********************************** INTERNAL ************************************/
@@ -67,6 +74,7 @@
,SUBTYPE(M_C(name, _type_t)) \
,OPLIST(oplist) \
,M_IF_METHOD(TEST_EMPTY, oplist)(TEST_EMPTY(M_C(name,_empty_p)),) \
,M_IF_METHOD(GET_SIZE, oplist)(GET_SIZE(M_C(name,_size)),) \
,M_IF_METHOD(CLEAN, oplist)(CLEAN(M_C(name,_clean)),) \
,M_IF_METHOD(KEY_TYPE, oplist)(KEY_TYPE(M_GET_KEY_TYPE oplist),) \
,M_IF_METHOD(VALUE_TYPE, oplist)(VALUE_TYPE(M_GET_VALUE_TYPE oplist),) \
@@ -90,7 +98,7 @@
)
#define CONCURRENTI_CONTRACT(c) do { \
assert ((c)->self == c); \
assert ((c) != NULL); \
} while (0)
// Deferred evaluation for the concurrent definition.
@@ -111,14 +119,87 @@
\
typedef type M_C(name, _type_t); \
\
\
M_IF_METHOD(INIT, oplist)( \
/* Define the lock strategy (global & shared lock) */ \
static inline void \
M_C(name, _init)(concurrent_t out) \
M_C(name, _internal_init)(concurrent_t out) \
{ \
m_mutex_init(out->lock); \
m_cond_init(out->there_is_data); \
out->self = out; \
} \
\
static inline void \
M_C(name, _internal_clear)(concurrent_t out) \
{ \
assert (out->self == out); \
m_mutex_clear(out->lock); \
m_cond_clear(out->there_is_data); \
out->self = NULL; \
} \
\
static inline void \
M_C(name, _read_lock)(const concurrent_t out) \
{ \
assert (out->self == out); \
m_mutex_lock (out->self->lock); \
} \
\
static inline void \
M_C(name, _read_unlock)(const concurrent_t out) \
{ \
assert (out->self == out); \
m_mutex_unlock (out->self->lock); \
} \
\
static inline void \
M_C(name, _read_wait)(int *p, const concurrent_t out) \
{ \
assert (out->self == out); \
(void) p; \
m_cond_wait(out->self->there_is_data, out->self->lock); \
} \
\
static inline void \
M_C(name, _read_wait_unlock)(int *p, const concurrent_t out) \
{ \
(void) p; \
m_mutex_unlock (out->self->lock); \
} \
\
static inline void \
M_C(name, _write_lock)(concurrent_t out) \
{ \
m_mutex_lock (out->lock); \
} \
\
static inline void \
M_C(name, _write_unlock)(concurrent_t out) \
{ \
m_mutex_unlock (out->lock); \
} \
\
static inline void \
M_C(name, _write_wait)(const concurrent_t out) \
{ \
m_cond_wait(out->self->there_is_data, out->self->lock); \
} \
\
static inline void \
M_C(name, _write_signal)(concurrent_t out) \
{ \
m_cond_broadcast(out->there_is_data); \
} \
\
CONCURRENTI_DEF_FUNC(name, type, oplist, concurrent_t, concurrent_it_t)
// Internal definition of the functions.
#define CONCURRENTI_DEF_FUNC(name, type, oplist, concurrent_t, concurrent_it_t) \
M_IF_METHOD(INIT, oplist)( \
static inline void \
M_C(name, _init)(concurrent_t out) \
{ \
M_C(name, _internal_init)(out); \
M_CALL_INIT(oplist, out->data); \
CONCURRENTI_CONTRACT(out); \
} \
@@ -130,12 +211,10 @@
{ \
CONCURRENTI_CONTRACT(src); \
assert (out != src); \
m_mutex_init(out->lock); \
m_cond_init(out->there_is_data); \
out->self = out; \
m_mutex_lock (src->self->lock); \
M_C(name, _internal_init)(out); \
M_C(name, _read_lock)(src); \
M_CALL_INIT_SET(oplist, out->data, src->data); \
m_mutex_unlock(src->self->lock); \
M_C(name, _read_unlock)(src); \
CONCURRENTI_CONTRACT(out); \
} \
,) \
@@ -147,19 +226,19 @@
CONCURRENTI_CONTRACT(out); \
if (out == src) return; \
if (out < src) { \
m_mutex_lock (out->lock); \
m_mutex_lock (src->self->lock); \
M_C(name, _write_lock)(out); \
M_C(name, _read_lock)(src); \
} else { \
m_mutex_lock (src->self->lock); \
m_mutex_lock (out->lock); \
M_C(name, _read_lock)(src); \
M_C(name, _write_lock)(out); \
} \
M_CALL_SET(oplist, out->data, src->data); \
if (out < src) { \
m_mutex_unlock (src->self->lock); \
m_mutex_unlock (out->lock); \
M_C(name, _read_lock)(src); \
M_C(name, _write_unlock)(out); \
} else { \
m_mutex_unlock (out->lock); \
m_mutex_unlock (src->self->lock); \
M_C(name, _write_unlock)(out); \
M_C(name, _read_unlock)(src); \
} \
CONCURRENTI_CONTRACT(out); \
} \
@@ -172,8 +251,7 @@
CONCURRENTI_CONTRACT(out); \
/* No need to lock */ \
M_CALL_CLEAR(oplist, out->data); \
m_mutex_clear (out->lock); \
m_cond_clear(out->there_is_data); \
M_C(name, _internal_clear)(out); \
} \
,) \
\
@@ -184,12 +262,9 @@
CONCURRENTI_CONTRACT(src); \
assert (out != src); \
/* No need to lock 'src' ? */ \
m_mutex_init (out->lock); \
m_cond_init (out->there_is_data); \
out->self = out; \
M_C(name, _internal_init)(out); \
M_CALL_INIT_MOVE(oplist, out->data, src->data); \
m_mutex_clear (src->lock); \
m_cond_clear (src->there_is_data); \
M_C(name, _internal_clear)(src); \
CONCURRENTI_CONTRACT(out); \
} \
,) \
@@ -201,11 +276,10 @@
CONCURRENTI_CONTRACT(out); \
CONCURRENTI_CONTRACT(src); \
/* No need to lock 'src' ? */ \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_MOVE(oplist, out->data, src->data); \
m_mutex_unlock (out->lock); \
m_mutex_clear (src->lock); \
m_cond_clear (src->there_is_data); \
M_C(name, _write_unlock)(out); \
M_C(name, _internal_clear)(src); \
CONCURRENTI_CONTRACT(out); \
} \
,) \
@@ -217,19 +291,19 @@
CONCURRENTI_CONTRACT(out); \
CONCURRENTI_CONTRACT(src); \
if (out < src) { \
m_mutex_lock (out->lock); \
m_mutex_lock (src->lock); \
M_C(name, _write_lock)(out); \
M_C(name, _write_lock)(src); \
} else { \
m_mutex_lock (src->lock); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(src); \
M_C(name, _write_lock)(out); \
} \
M_CALL_SWAP(oplist, out->data, src->data); \
if (out < src) { \
m_mutex_unlock (src->lock); \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(src); \
M_C(name, _write_unlock)(out); \
} else { \
m_mutex_unlock (out->lock); \
m_mutex_unlock (src->lock); \
M_C(name, _write_unlock)(out); \
M_C(name, _write_unlock)(src); \
} \
} \
,) \
@@ -239,9 +313,9 @@
M_C(name, _clean)(concurrent_t out) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_CLEAN(oplist, out->data); \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
@@ -250,11 +324,23 @@
M_C(name, _empty_p)(concurrent_t const out) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->self->lock); \
M_C(name, _read_lock)(out); \
bool b = M_CALL_TEST_EMPTY(oplist, out->data); \
m_mutex_unlock (out->self->lock); \
M_C(name, _read_unlock)(out); \
return b; \
} \
,) \
\
M_IF_METHOD(GET_SIZE, oplist)( \
static inline size_t \
M_C(name, _size)(concurrent_t const out) \
{ \
CONCURRENTI_CONTRACT(out); \
M_C(name, _read_lock)(out); \
size_t r = M_CALL_GET_SIZE(oplist, out->data); \
M_C(name, _read_unlock)(out); \
return r; \
} \
,) \
\
M_IF_METHOD(SET_KEY, oplist)( \
@@ -262,25 +348,25 @@
M_C(name, _set_at)(concurrent_t out, M_GET_KEY_TYPE oplist const key, M_GET_VALUE_TYPE oplist const data) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_SET_KEY(oplist, out->data, key, data); \
m_cond_broadcast(out->there_is_data); \
m_mutex_unlock (out->lock); \
M_C(name, _write_signal)(out); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
M_IF_METHOD(GET_KEY, oplist)( \
static inline bool \
M_C(name, _get_copy)(M_GET_VALUE_TYPE oplist *out_data, concurrent_t out, M_GET_KEY_TYPE oplist const key) \
M_C(name, _get_copy)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key) \
{ \
CONCURRENTI_CONTRACT(out); \
assert (out_data != NULL); \
m_mutex_lock (out->lock); \
M_C(name, _read_lock)(out); \
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
if (p != NULL) { \
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
} \
m_mutex_unlock (out->lock); \
M_C(name, _read_unlock)(out); \
return p != NULL; \
} \
,) \
@@ -291,11 +377,11 @@
{ \
CONCURRENTI_CONTRACT(out); \
assert (out_data != NULL); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_SET_KEY(oplist, out->data, key); \
assert (p != NULL); \
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
@@ -304,9 +390,9 @@
M_C(name, _erase)(concurrent_t out, M_GET_KEY_TYPE oplist const key) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
bool b = M_CALL_ERASE_KEY(oplist, out->data, key); \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
return b; \
} \
,) \
@@ -316,10 +402,10 @@
M_C(name, _push)(concurrent_t out, M_GET_SUBTYPE oplist const data) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_PUSH(oplist, out->data, data); \
m_cond_broadcast(out->there_is_data); \
m_mutex_unlock (out->lock); \
M_C(name, _write_signal)(out); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
@@ -328,9 +414,9 @@
M_C(name, _pop)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_POP(oplist, p, out->data); \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
@@ -339,10 +425,10 @@
M_C(name, _push_move)(concurrent_t out, M_GET_SUBTYPE oplist *data) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_PUSH_MOVE(oplist, out->data, data); \
m_cond_broadcast(out->there_is_data); \
m_mutex_unlock (out->lock); \
M_C(name, _write_signal)(out); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
@@ -351,9 +437,9 @@
M_C(name, _pop_move)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
M_CALL_POP_MOVE(oplist, p, out->data); \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
} \
,) \
\
@@ -362,9 +448,9 @@
M_C(name, _get_str)(string_t str, concurrent_t const out, bool a) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->self->lock); \
M_C(name, _read_lock)(out); \
M_CALL_GET_STR(oplist, str, out->data, a); \
m_mutex_unlock (out->self->lock); \
M_C(name, _read_unlock)(out); \
} \
,) \
\
@@ -373,9 +459,9 @@
M_C(name, _out_str)(FILE *f, concurrent_t const out) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->self->lock); \
M_C(name, _read_lock)(out); \
M_CALL_OUT_STR(oplist, f, out->data); \
m_mutex_unlock (out->self->lock); \
M_C(name, _read_unlock)(out); \
} \
,) \
\
@@ -384,9 +470,10 @@
M_C(name, _parse_str)(concurrent_t out, const char str[], const char **e) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
bool b = M_CALL_PARSE_STR(oplist, out->data, str, e); \
m_mutex_unlock (out->lock); \
M_C(name, _write_signal)(out); \
M_C(name, _write_unlock)(out); \
return b; \
} \
,) \
@@ -396,9 +483,10 @@
M_C(name, _in_str)(concurrent_t out, FILE *f) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
bool b = M_CALL_IN_STR(oplist, out->data, f); \
m_mutex_unlock (out->lock); \
M_C(name, _write_signal)(out); \
M_C(name, _write_unlock)(out); \
return b; \
} \
,) \
@@ -411,19 +499,19 @@
CONCURRENTI_CONTRACT(out2); \
if (out1 == out2) return true; \
if (out1 < out2) { \
m_mutex_lock (out1->self->lock); \
m_mutex_lock (out2->self->lock); \
M_C(name, _read_lock)(out1); \
M_C(name, _read_lock)(out2); \
} else { \
m_mutex_lock (out2->self->lock); \
m_mutex_lock (out1->self->lock); \
M_C(name, _read_lock)(out2); \
M_C(name, _read_lock)(out1); \
} \
bool b = M_CALL_EQUAL(oplist, out1->data, out2->data); \
if (out1 < out2) { \
m_mutex_unlock (out2->self->lock); \
m_mutex_unlock (out1->self->lock); \
M_C(name, _read_unlock)(out2); \
M_C(name, _read_unlock)(out1); \
} else { \
m_mutex_unlock (out1->self->lock); \
m_mutex_unlock (out2->self->lock); \
M_C(name, _read_unlock)(out1); \
M_C(name, _read_unlock)(out2); \
} \
return b; \
} \
@@ -431,12 +519,13 @@
\
M_IF_METHOD(GET_KEY, oplist)( \
static inline bool \
M_C(name, _get_blocking)(M_GET_VALUE_TYPE oplist *out_data, concurrent_t out, M_GET_KEY_TYPE oplist const key, bool blocking) \
M_C(name, _get_blocking)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key, bool blocking) \
{ \
CONCURRENTI_CONTRACT(out); \
assert (out_data != NULL); \
bool ret = false; \
m_mutex_lock (out->lock); \
int count = 0; \
M_C(name, _read_lock)(out); \
while (true) { \
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
if (p != NULL) { \
@@ -445,9 +534,9 @@
break; \
} \
if (blocking == false) break; \
m_cond_wait(out->there_is_data, out->lock); \
M_C(name, _read_wait)(&count, out); \
} \
m_mutex_unlock (out->lock); \
M_C(name, _read_wait_unlock)(&count, out); \
return ret; \
} \
,) \
@@ -459,7 +548,7 @@
CONCURRENTI_CONTRACT(out); \
assert (p != NULL); \
bool ret = false; \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
while (true) { \
if (!M_CALL_TEST_EMPTY(oplist, out->data)) { \
M_CALL_POP(oplist, p, out->data); \
@@ -467,9 +556,9 @@
break; \
} \
if (blocking == false) break; \
m_cond_wait(out->there_is_data, out->lock); \
M_C(name, _write_wait)(out); \
} \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
return ret; \
} \
,) \
@@ -481,7 +570,7 @@
CONCURRENTI_CONTRACT(out); \
assert (p != NULL); \
bool ret = false; \
m_mutex_lock (out->lock); \
M_C(name, _write_lock)(out); \
while (true) { \
if (!M_CALL_TEST_EMPTY(oplist, out->data)) { \
M_CALL_POP_MOVE(oplist, p, out->data); \
@@ -489,9 +578,9 @@
break; \
} \
if (blocking == false) break; \
m_cond_wait(out->there_is_data, out->lock); \
M_C(name, _write_wait)(out); \
} \
m_mutex_unlock (out->lock); \
M_C(name, _write_unlock)(out); \
return ret; \
} \
,) \
@@ -501,11 +590,127 @@
M_C(name, _hash)(concurrent_t const out) \
{ \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->self->lock); \
M_C(name, _read_lock)(out); \
size_t h = M_CALL_HASH(oplist, out->data); \
m_mutex_unlock (out->self->lock); \
M_C(name, _read_unlock)(out); \
return h; \
} \
,) \
// Deferred evaluation for the concurrent definition.
#define CONCURRENTI_RP_DEF(arg) CONCURRENTI_RP_DEF2 arg
// Internal definition.
#define CONCURRENTI_RP_DEF2(name, type, oplist, concurrent_t, concurrent_it_t) \
\
typedef struct M_C(name, _s) { \
struct M_C(name, _s) *self; \
m_mutex_t lock; \
m_mutex_t read_lock; \
size_t read_count; \
m_cond_t there_is_data; /* condition raised when there is data */ \
type data; \
} concurrent_t[1]; \
\
typedef struct M_C(name, _s) *M_C(name, _ptr); \
typedef const struct M_C(name, _s) *M_C(name, _srcptr); \
\
typedef type M_C(name, _type_t); \
\
/* Define the lock strategy (multi lock) */ \
static inline void \
M_C(name, _internal_init)(concurrent_t out) \
{ \
m_mutex_init(out->lock); \
m_mutex_init(out->read_lock); \
m_cond_init(out->there_is_data); \
out->self = out; \
out->read_count = 0; \
} \
\
static inline void \
M_C(name, _internal_clear)(concurrent_t out) \
{ \
assert (out->self == out); \
m_mutex_clear(out->lock); \
m_mutex_clear(out->read_lock); \
m_cond_clear(out->there_is_data); \
out->self = NULL; \
} \
\
static inline void \
M_C(name, _read_lock)(const concurrent_t out) \
{ \
struct M_C(name, _s) *self = out->self; \
assert (self == out); \
m_mutex_lock (self->read_lock); \
self->read_count ++; \
if (self->read_count == 1) { \
m_mutex_lock (self->lock); \
} \
m_mutex_unlock (self->read_lock); \
} \
\
static inline void \
M_C(name, _read_unlock)(const concurrent_t out) \
{ \
struct M_C(name, _s) *self = out->self; \
assert (self == out); \
m_mutex_lock (self->read_lock); \
self->read_count --; \
if (self->read_count == 0) { \
m_mutex_unlock (self->lock); \
} \
m_mutex_unlock (self->read_lock); \
} \
\
static inline void \
M_C(name, _write_lock)(concurrent_t out) \
{ \
m_mutex_lock (out->lock); \
} \
\
static inline void \
M_C(name, _write_unlock)(concurrent_t out) \
{ \
m_mutex_unlock (out->lock); \
} \
\
static inline void \
M_C(name, _read_wait)(int *p, const concurrent_t out) \
{ \
struct M_C(name, _s) *self = out->self; \
assert (self == out); \
if (*p == 0) { \
m_mutex_unlock (self->read_lock); \
m_mutex_lock (self->lock); \
*p = 1; \
} else { \
m_cond_wait(self->there_is_data, self->lock); \
} \
} \
\
static inline void \
M_C(name, _read_wait_unlock)(int *p, const concurrent_t out) \
{ \
m_mutex_unlock (*p == 0 ? out->self->read_lock : out->self->lock); \
} \
\
static inline void \
M_C(name, _write_wait)(const concurrent_t out) \
{ \
m_cond_wait(out->self->there_is_data, out->self->lock); \
} \
\
static inline void \
M_C(name, _write_signal)(concurrent_t out) \
{ \
m_cond_broadcast(out->there_is_data); \
} \
\
CONCURRENTI_DEF_FUNC(name, type, oplist, concurrent_t, concurrent_it_t)
#endif
+3
View File
@@ -1793,6 +1793,7 @@ m_core_hash (const void *str, size_t length)
#define M_SET_KEY_SET_KEY(a) ,a,
#define M_GET_SET_KEY_GET_SET_KEY(a) ,a,
#define M_ERASE_KEY_ERASE_KEY(a) ,a,
#define M_GET_SIZE_GET_SIZE(a) ,a,
#define M_PUSH_PUSH(a) ,a,
#define M_POP_POP(a) ,a,
#define M_PUSH_MOVE_PUSH_MOVE(a) ,a,
@@ -1869,6 +1870,7 @@ m_core_hash (const void *str, size_t length)
#define M_GET_SET_KEY(...) M_GET_METHOD(SET_KEY, M_NO_DEFAULT, __VA_ARGS__)
#define M_GET_GET_SET_KEY(...) M_GET_METHOD(GET_SET_KEY, M_NO_DEFAULT, __VA_ARGS__)
#define M_GET_ERASE_KEY(...) M_GET_METHOD(ERASE_KEY, M_NO_DEFAULT, __VA_ARGS__)
#define M_GET_GET_SIZE(...) M_GET_METHOD(GET_SIZE, M_NO_DEFAULT, __VA_ARGS__)
#define M_GET_PUSH(...) M_GET_METHOD(PUSH, M_NO_DEFAULT, __VA_ARGS__)
#define M_GET_POP(...) M_GET_METHOD(POP, M_NO_DEFAULT, __VA_ARGS__)
#define M_GET_PUSH_MOVE(...) M_GET_METHOD(PUSH_MOVE, M_NO_DEFAULT, __VA_ARGS__)
@@ -1934,6 +1936,7 @@ m_core_hash (const void *str, size_t length)
#define M_CALL_SET_KEY(oplist, ...) M_APPLY_API(M_GET_SET_KEY oplist, oplist, __VA_ARGS__)
#define M_CALL_GET_SET_KEY(oplist, ...) M_APPLY_API(M_GET_GET_SET_KEY oplist, oplist, __VA_ARGS__)
#define M_CALL_ERASE_KEY(oplist, ...) M_APPLY_API(M_GET_ERASE_KEY oplist, oplist, __VA_ARGS__)
#define M_CALL_GET_SIZE(oplist, ...) M_APPLY_API(M_GET_GET_SIZE oplist, oplist, __VA_ARGS__)
#define M_CALL_PUSH(oplist, ...) M_APPLY_API(M_GET_PUSH oplist, oplist, __VA_ARGS__)
#define M_CALL_POP(oplist, ...) M_APPLY_API(M_GET_POP oplist, oplist, __VA_ARGS__)
#define M_CALL_PUSH_MOVE(oplist, ...) M_APPLY_API(M_GET_PUSH_MOVE oplist, oplist, __VA_ARGS__)
+1
View File
@@ -888,6 +888,7 @@
,IT_REF(M_C(name,_ref)) \
,IT_CREF(M_C(name,_cref)) \
,CLEAN(M_C(name,_clean)) \
,GET_SIZE(M_C(name, _size)) \
,PUSH(M_C(name,_push_back)) \
,POP(M_C(name,_pop_back)) \
,OPLIST(oplist) \
+2
View File
@@ -842,6 +842,7 @@
,ERASE_KEY(M_C(name, _erase)) \
,KEY_OPLIST(key_oplist) \
,VALUE_OPLIST(value_oplist) \
,GET_SIZE(M_C(name, _size)) \
,M_IF_METHOD_BOTH(GET_STR, key_oplist, value_oplist)(GET_STR(M_C(name, _get_str)),) \
,M_IF_METHOD_BOTH(PARSE_STR, key_oplist, value_oplist)(PARSE_STR(M_C(name, _parse_str)),) \
,M_IF_METHOD_BOTH(OUT_STR, key_oplist, value_oplist)(OUT_STR(M_C(name, _out_str)),) \
@@ -875,6 +876,7 @@
ERASE_KEY(M_C(name, _erase)), \
KEY_OPLIST(oplist), \
VALUE_OPLIST(oplist), \
GET_SIZE(M_C(name, _size)), \
IT_TYPE(M_C(name, _it_t)), \
IT_FIRST(M_C(name,_it)), \
IT_SET(M_C(name, _it_set)), \
+12
View File
@@ -120,6 +120,7 @@ static inline void m_oncei_call(m_oncei_t o, void (*func)(void))
call_once(o,func);
}
#define M_THREAD_ATTR _Thread_local
// MSYS2 doesn't define _WIN32 by default.
@@ -237,6 +238,12 @@ static inline void m_oncei_call(m_oncei_t o, void (*func)(void))
InitOnceExecuteOnce(o, m_oncei_callback, (void*)(intptr_t)func, NULL);
}
#if defined(_MSC_VER)
# define M_THREAD_ATTR __declspec( thread )
#else
# define M_THREAD_ATTR __thread
#endif
#else
/**************************** PTHREAD version ******************************/
@@ -348,8 +355,13 @@ static inline void m_oncei_call(m_oncei_t o, void (*func)(void))
pthread_once(o,func);
}
#define M_THREAD_ATTR __thread
#endif
// TODO: m_thread_sleep doesn't yield thread during waiting, making it a poor choice for active waiting.
// TODO: Obsolete M_LOCK macro.
/* M_LOCK macro. Allow simple locking encapsulation.
USAGE:
static M_LOCK_DECL(name);
+2
View File
@@ -65,6 +65,8 @@
,PUSH(M_C(name,_push)) \
,POP(M_C(name,_pop)) \
,OPLIST(oplist) \
,TEST_EMPTY(M_C(name, _empty_p)), \
,GET_SIZE(M_C(name, _size)) \
)
#define PRIOQUEUEI_DEF2(name, type, oplist, prioqueue_t, it_t) \
+1
View File
@@ -61,6 +61,7 @@
TYPE(M_C(name,_t)), \
SUBTYPE(M_C(name, _type_t)), \
TEST_EMPTY(M_C(name,_empty_p)), \
GET_SIZE(M_C(name, _size)), \
IT_TYPE(M_C(name, _it_t)), \
IT_FIRST(M_C(name,_it)), \
IT_SET(M_C(name,_it_set)), \
+9 -1
View File
@@ -128,7 +128,15 @@ static void test_uint(void)
assert(*array_uint_cget(v2, 500) == 0);
assert(array_uint_size(v2) == 1005);
assert(*array_uint_cget(v2, 1004) == 0);
array_uint_set_at(v2, 1004, 465487);
array_uint_insert_v (v2, 500, 0);
assert(array_uint_size(v2) == 1005);
assert(*array_uint_cget(v2, 1004) == 465487);
array_uint_insert_v (v2, 1004, 10000);
assert(array_uint_size(v2) == 11005);
assert(*array_uint_cget(v2, 11004) == 465487);
array_uint_resize(v2, 1005);
array_uint_init(v);
array_uint_swap(v,v2);
assert(array_uint_size(v) == 1005);
+25
View File
@@ -78,6 +78,12 @@ TUPLE_DEF2(my_tuple, (pool, string_pool_ts_t))
#define MY_TUPLE_OPLIST TUPLE_OPLIST(my_tuple, STRING_POOL_TS_OPLIST)
#define M_OPL_my_tuple_t() MY_TUPLE_OPLIST
ARRAY_DEF(key_list, string_t)
#define M_OPL_key_list_t() ARRAY_OPLIST(key_list, STRING_OPLIST)
CONCURRENT_DEF(key_list_ts, key_list_t)
CONCURRENT_RP_DEF(rpdict1, dict1_t, DICT_OPLIST(dict1))
/********************************/
parray1_t arr;
@@ -118,6 +124,7 @@ static void test_basic(void)
bool b = pdict1_get_blocking(&z, dict, 2, false);
assert (b);
assert (z == 3);
assert (pdict1_size(dict) == 1);
pdict1_clear(dict);
M_LET(str, string_t)
@@ -132,8 +139,26 @@ static void test_basic(void)
}
}
static void test_rp_basic(void)
{
rpdict1_t dict;
rpdict1_init(dict);
int z;
rpdict1_set_at(dict, 2, 3);
rpdict1_get_copy(&z, dict, 2);
assert (z == 3);
z = 1;
bool b = rpdict1_get_blocking(&z, dict, 2, false);
assert (b);
assert (z == 3);
assert (rpdict1_size(dict) == 1);
rpdict1_clear(dict);
}
int main(void)
{
test_basic();
test_rp_basic();
test();
exit(0);
}