Add more comments to explain how it works.

This commit is contained in:
Patrick Pelissier committed 2020-11-14 22:45:39 +01:00
1 parent 978e0ea5d7
commit 983a63fbbc
1 file changed
+76 -13
+76 -13
View File
@@ -141,10 +141,11 @@
) )
/* Internal contract /* Internal contract
Nothing notable. Can't check too much without locking the structure itself NOTE: Can't check too much without locking the container itself
*/ */
#define CONCURRENTI_CONTRACT(c) do { \ #define CONCURRENTI_CONTRACT(c) do { \
M_ASSERT ((c) != NULL); \ M_ASSERT ((c) != NULL); \
M_ASSERT ( (c)->self == (c)); \
} while (0) } while (0)
/* Deferred evaluation for the concurrent definition, /* Deferred evaluation for the concurrent definition,
@@ -186,66 +187,98 @@
/* Don't define iterator as it cannot be reliable in a concurrent type */ \ /* Don't define iterator as it cannot be reliable in a concurrent type */ \
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \ M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
\ \
/* Define the lock strategy (global & shared lock) */ \ /* Define the internal services used for the lock strategy */ \
\
/* Initial the fields of the concurrent object not associated to the \
sub-container. */ \
static inline void \ static inline void \
M_C(name, _internal_init)(concurrent_t out) \ M_C(name, _internal_init)(concurrent_t out) \
{ \ { \
m_mutex_init(out->lock); \ m_mutex_init(out->lock); \
m_cond_init(out->there_is_data); \ m_cond_init(out->there_is_data); \
out->self = out; \ out->self = out; \
CONCURRENTI_CONTRACT(out); \
} \ } \
\ \
/* Clear the fields of the concurrent object not associated to the \
sub-container. */ \
static inline void \ static inline void \
M_C(name, _internal_clear)(concurrent_t out) \ M_C(name, _internal_clear)(concurrent_t out) \
{ \ { \
M_ASSERT (out->self == out); \ CONCURRENTI_CONTRACT(out); \
m_mutex_clear(out->lock); \ m_mutex_clear(out->lock); \
m_cond_clear(out->there_is_data); \ m_cond_clear(out->there_is_data); \
out->self = NULL; \ out->self = NULL; \
} \ } \
\ \
/* Get the read lock. Multiple threads can get it, but only for reading. \
write lock is exclusive. \
NOTE: This instance doesn't implement the read/write strategy, \
and only get the lock */ \
static inline void \ static inline void \
M_C(name, _read_lock)(const concurrent_t out) \ M_C(name, _read_lock)(const concurrent_t out) \
{ \ { \
M_ASSERT (out->self == out); \ CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->self->lock); \ m_mutex_lock (out->self->lock); \
} \ } \
\ \
/* Free the read lock. See above. \
NOTE: This instance doesn't implement the read/write strategy, \
and only get the lock */ \
static inline void \ static inline void \
M_C(name, _read_unlock)(const concurrent_t out) \ M_C(name, _read_unlock)(const concurrent_t out) \
{ \ { \
M_ASSERT (out->self == out); \ CONCURRENTI_CONTRACT(out); \
m_mutex_unlock (out->self->lock); \ m_mutex_unlock (out->self->lock); \
} \ } \
\ \
/* Wait for a thread pushing some data in the container. \
CONSTRAINT: the read lock shall be get before calling this service */ \
static inline void \ static inline void \
M_C(name, _read_wait)(const concurrent_t out) \ M_C(name, _read_wait)(const concurrent_t out) \
{ \ { \
M_ASSERT (out->self == out); \ CONCURRENTI_CONTRACT(out); \
m_cond_wait(out->self->there_is_data, out->self->lock); \ m_cond_wait(out->self->there_is_data, out->self->lock); \
} \ } \
\ \
/* Get the write lock. Only one threads can get it, and no other threads \
can get the read lock too. \
NOTE: This instance doesn't implement the read/write strategy, \
and only get the lock */ \
static inline void \ static inline void \
M_C(name, _write_lock)(concurrent_t out) \ M_C(name, _write_lock)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \ m_mutex_lock (out->lock); \
} \ } \
\ \
/* Free the write lock. \
NOTE: This instance doesn't implement the read/write strategy, \
and only get the lock */ \
static inline void \ static inline void \
M_C(name, _write_unlock)(concurrent_t out) \ M_C(name, _write_unlock)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_mutex_unlock (out->lock); \ m_mutex_unlock (out->lock); \
} \ } \
\ \
/* Wait for a thread pushing some data in the container. \
CONSTRAINT: the write lock shall be get before calling this service */ \
static inline void \ static inline void \
M_C(name, _write_wait)(const concurrent_t out) \ M_C(name, _write_wait)(const concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_cond_wait(out->self->there_is_data, out->self->lock); \ m_cond_wait(out->self->there_is_data, out->self->lock); \
} \ } \
\ \
/* Wait to all threads that some data are available in the container. \
CONSTRAINT: the write lock shall be get before calling this service */ \
static inline void \ static inline void \
M_C(name, _write_signal)(concurrent_t out) \ M_C(name, _write_signal)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
/* We need to signal this to ALL waiting threads as multiple threads \
may wait on a some data of this container. */ \
m_cond_broadcast(out->there_is_data); \ m_cond_broadcast(out->there_is_data); \
} \ } \
\ \
@@ -257,6 +290,8 @@
- type: type of the sub container - type: type of the sub container
- oplist: oplist of the type of the sub container - oplist: oplist of the type of the sub container
- concurrent_t: alias for M_C(name, _t) [ type of the container ] - concurrent_t: alias for M_C(name, _t) [ type of the container ]
A function is defined only if the underlying container exports the needed
services. It is usually one service declared per service exported.
*/ */
#define CONCURRENTI_DEF_FUNC_P3(name, type, oplist, concurrent_t) \ #define CONCURRENTI_DEF_FUNC_P3(name, type, oplist, concurrent_t) \
\ \
@@ -289,7 +324,17 @@
M_C(name, _set)(concurrent_t out, concurrent_t const src) \ M_C(name, _set)(concurrent_t out, concurrent_t const src) \
{ \ { \
CONCURRENTI_CONTRACT(out); \ CONCURRENTI_CONTRACT(out); \
if (out == src) return; \ if (M_UNLIKELY (out == src)) return; \
/* Need to order the locks in a total way to avoid lock deadlock. \
Indeed, two call to _set can be done in two threads with : \
T1: A := B \
T2: B := A \
If we lock first the mutex of out, then the src, it could be possible \
in the previous scenario that both mutexs are locked: T1 has locked A \
and T2 has locked B, and T1 is waiting for locking B, and T2 is waiting \
for locking A, resulting in a deadlock. \
To avoid this problem, we **always** lock the mutex which address is \
the lowest. */ \
if (out < src) { \ if (out < src) { \
M_C(name, _write_lock)(out); \ M_C(name, _write_lock)(out); \
M_C(name, _read_lock)(src); \ M_C(name, _read_lock)(src); \
@@ -314,7 +359,8 @@
M_C(name, _clear)(concurrent_t out) \ M_C(name, _clear)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \ CONCURRENTI_CONTRACT(out); \
/* No need to lock */ \ /* No need to lock. A clear is supposed to be called when all operations \
of the container in other threads are terminated */ \
M_CALL_CLEAR(oplist, out->data); \ M_CALL_CLEAR(oplist, out->data); \
M_C(name, _internal_clear)(out); \ M_C(name, _internal_clear)(out); \
} \ } \
@@ -355,6 +401,8 @@
{ \ { \
CONCURRENTI_CONTRACT(out); \ CONCURRENTI_CONTRACT(out); \
CONCURRENTI_CONTRACT(src); \ CONCURRENTI_CONTRACT(src); \
if (M_UNLIKELY (out == src)) return; \
/* See comment above */ \
if (out < src) { \ if (out < src) { \
M_C(name, _write_lock)(out); \ M_C(name, _write_lock)(out); \
M_C(name, _write_lock)(src); \ M_C(name, _write_lock)(src); \
@@ -457,6 +505,8 @@
CONCURRENTI_CONTRACT(out); \ CONCURRENTI_CONTRACT(out); \
M_C(name, _write_lock)(out); \ M_C(name, _write_lock)(out); \
bool b = M_CALL_ERASE_KEY(oplist, out->data, key); \ bool b = M_CALL_ERASE_KEY(oplist, out->data, key); \
/* We suppose that the container has 'infinite' capacity, so \
we won't signal that a free space has been created */ \
M_C(name, _write_unlock)(out); \ M_C(name, _write_unlock)(out); \
return b; \ return b; \
} \ } \
@@ -481,6 +531,7 @@
CONCURRENTI_CONTRACT(out); \ CONCURRENTI_CONTRACT(out); \
M_C(name, _write_lock)(out); \ M_C(name, _write_lock)(out); \
M_CALL_POP(oplist, p, out->data); \ M_CALL_POP(oplist, p, out->data); \
/* See comment above */ \
M_C(name, _write_unlock)(out); \ M_C(name, _write_unlock)(out); \
} \ } \
,) \ ,) \
@@ -504,6 +555,7 @@
CONCURRENTI_CONTRACT(out); \ CONCURRENTI_CONTRACT(out); \
M_C(name, _write_lock)(out); \ M_C(name, _write_lock)(out); \
M_CALL_POP_MOVE(oplist, p, out->data); \ M_CALL_POP_MOVE(oplist, p, out->data); \
/* See comment above */ \
M_C(name, _write_unlock)(out); \ M_C(name, _write_unlock)(out); \
} \ } \
,) \ ,) \
@@ -587,7 +639,8 @@
{ \ { \
CONCURRENTI_CONTRACT(out1); \ CONCURRENTI_CONTRACT(out1); \
CONCURRENTI_CONTRACT(out2); \ CONCURRENTI_CONTRACT(out2); \
if (out1 == out2) return true; \ if (M_UNLIKELY (out1 == out2)) return true; \
/* See comment above on mutal mutexs */ \
if (out1 < out2) { \ if (out1 < out2) { \
M_C(name, _read_lock)(out1); \ M_C(name, _read_lock)(out1); \
M_C(name, _read_lock)(out2); \ M_C(name, _read_lock)(out2); \
@@ -623,6 +676,7 @@
break; \ break; \
} \ } \
if (blocking == false) break; \ if (blocking == false) break; \
/* No data: wait for a write to signal some data */ \
M_C(name, _read_wait)(out); \ M_C(name, _read_wait)(out); \
} \ } \
M_C(name, _read_unlock)(out); \ M_C(name, _read_unlock)(out); \
@@ -645,6 +699,7 @@
break; \ break; \
} \ } \
if (blocking == false) break; \ if (blocking == false) break; \
/* No data: wait for a write to signal some data */ \
M_C(name, _write_wait)(out); \ M_C(name, _write_wait)(out); \
} \ } \
M_C(name, _write_unlock)(out); \ M_C(name, _write_unlock)(out); \
@@ -667,6 +722,7 @@
break; \ break; \
} \ } \
if (blocking == false) break; \ if (blocking == false) break; \
/* No data: wait for a write to signal some data */ \
M_C(name, _write_wait)(out); \ M_C(name, _write_wait)(out); \
} \ } \
M_C(name, _write_unlock)(out); \ M_C(name, _write_unlock)(out); \
@@ -682,6 +738,7 @@
M_C(name, _read_lock)(out); \ M_C(name, _read_lock)(out); \
size_t h = M_CALL_HASH(oplist, out->data); \ size_t h = M_CALL_HASH(oplist, out->data); \
M_C(name, _read_unlock)(out); \ M_C(name, _read_unlock)(out); \
/* The hash is unchanged by the concurrent container */ \
return h; \ return h; \
} \ } \
,) \ ,) \
@@ -723,7 +780,7 @@
\ \
typedef type M_C(name, _subtype_ct); \ typedef type M_C(name, _subtype_ct); \
\ \
/* Define the lock strategy (multi lock) */ \ /* Define the internal services for the lock strategy */ \
static inline void \ static inline void \
M_C(name, _internal_init)(concurrent_t out) \ M_C(name, _internal_init)(concurrent_t out) \
{ \ { \
@@ -733,12 +790,13 @@
out->self = out; \ out->self = out; \
out->read_count = 0; \ out->read_count = 0; \
out->writer_waiting = false; \ out->writer_waiting = false; \
CONCURRENTI_CONTRACT(out); \
} \ } \
\ \
static inline void \ static inline void \
M_C(name, _internal_clear)(concurrent_t out) \ M_C(name, _internal_clear)(concurrent_t out) \
{ \ { \
M_ASSERT (out->self == out); \ CONCURRENTI_CONTRACT(out); \
m_mutex_clear(out->lock); \ m_mutex_clear(out->lock); \
m_cond_clear(out->rw_done); \ m_cond_clear(out->rw_done); \
m_cond_clear(out->there_is_data); \ m_cond_clear(out->there_is_data); \
@@ -748,8 +806,8 @@
static inline void \ static inline void \
M_C(name, _read_lock)(const concurrent_t out) \ M_C(name, _read_lock)(const concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
struct M_C(name, _s) *self = out->self; \ struct M_C(name, _s) *self = out->self; \
M_ASSERT (self == out); \
m_mutex_lock (self->lock); \ m_mutex_lock (self->lock); \
while (self->writer_waiting == true) { \ while (self->writer_waiting == true) { \
m_cond_wait(self->rw_done, self->lock); \ m_cond_wait(self->rw_done, self->lock); \
@@ -761,8 +819,8 @@
static inline void \ static inline void \
M_C(name, _read_unlock)(const concurrent_t out) \ M_C(name, _read_unlock)(const concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
struct M_C(name, _s) *self = out->self; \ struct M_C(name, _s) *self = out->self; \
M_ASSERT (self == out); \
m_mutex_lock (self->lock); \ m_mutex_lock (self->lock); \
self->read_count --; \ self->read_count --; \
if (self->read_count == 0) { \ if (self->read_count == 0) { \
@@ -774,6 +832,7 @@
static inline void \ static inline void \
M_C(name, _write_lock)(concurrent_t out) \ M_C(name, _write_lock)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \ m_mutex_lock (out->lock); \
while (out->writer_waiting == true) { \ while (out->writer_waiting == true) { \
m_cond_wait(out->rw_done, out->lock); \ m_cond_wait(out->rw_done, out->lock); \
@@ -788,6 +847,7 @@
static inline void \ static inline void \
M_C(name, _write_unlock)(concurrent_t out) \ M_C(name, _write_unlock)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \ m_mutex_lock (out->lock); \
out->writer_waiting = false; \ out->writer_waiting = false; \
m_cond_broadcast (out->rw_done); \ m_cond_broadcast (out->rw_done); \
@@ -797,6 +857,7 @@
static inline void \ static inline void \
M_C(name, _read_wait)(const concurrent_t out) \ M_C(name, _read_wait)(const concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
struct M_C(name, _s) *self = out->self; \ struct M_C(name, _s) *self = out->self; \
M_ASSERT (self == out); \ M_ASSERT (self == out); \
m_mutex_lock (out->self->lock); \ m_mutex_lock (out->self->lock); \
@@ -815,6 +876,7 @@
static inline void \ static inline void \
M_C(name, _write_wait)(concurrent_t out) \ M_C(name, _write_wait)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \ m_mutex_lock (out->lock); \
out->writer_waiting = false; \ out->writer_waiting = false; \
m_cond_broadcast (out->rw_done); \ m_cond_broadcast (out->rw_done); \
@@ -832,6 +894,7 @@
static inline void \ static inline void \
M_C(name, _write_signal)(concurrent_t out) \ M_C(name, _write_signal)(concurrent_t out) \
{ \ { \
CONCURRENTI_CONTRACT(out); \
m_mutex_lock (out->lock); \ m_mutex_lock (out->lock); \
m_cond_broadcast(out->there_is_data); \ m_cond_broadcast(out->there_is_data); \
m_mutex_unlock (out->lock); \ m_mutex_unlock (out->lock); \