Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
|
|
|
|
#include "rendezvous.h"
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
#include "osdep/threads.h"
|
|
|
|
|
|
|
|
static mp_static_mutex lock = MP_STATIC_MUTEX_INITIALIZER;
|
|
|
|
static mp_cond wakeup = MP_STATIC_COND_INITIALIZER;
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
|
|
|
|
static struct waiter *waiters;
|
|
|
|
|
|
|
|
struct waiter {
|
|
|
|
void *tag;
|
|
|
|
struct waiter *next;
|
|
|
|
intptr_t *value;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* A barrier for 2 threads, which can exchange a value when they meet.
|
|
|
|
* The first thread to call this function will block. As soon as two threads
|
|
|
|
* are calling this function with the same tag value, they will unblock, and
|
2018-08-31 09:40:33 +00:00
|
|
|
* on each thread the call returns the value parameter of the _other_ thread.
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
*
|
|
|
|
* tag is an arbitrary value, but it must be an unique pointer. If there are
|
|
|
|
* more than 2 threads using the same tag, things won't work. Typically, it
|
|
|
|
* will have to point to a memory allocation or to the stack, while pointing
|
|
|
|
* it to static data is always a bug.
|
|
|
|
*
|
|
|
|
* This shouldn't be used for performance critical code (uses a linked list
|
|
|
|
* of _all_ waiters in the process, and temporarily wakes up _all_ waiters on
|
|
|
|
* each second call).
|
|
|
|
*
|
2024-02-08 09:51:33 +00:00
|
|
|
* This is inspired by: https://man.cat-v.org/plan_9/2/rendezvous */
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
intptr_t mp_rendezvous(void *tag, intptr_t value)
|
|
|
|
{
|
|
|
|
struct waiter wait = { .tag = tag, .value = &value };
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&lock);
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
struct waiter **prev = &waiters;
|
|
|
|
while (*prev) {
|
|
|
|
if ((*prev)->tag == tag) {
|
|
|
|
intptr_t tmp = *(*prev)->value;
|
|
|
|
*(*prev)->value = value;
|
|
|
|
value = tmp;
|
|
|
|
(*prev)->value = NULL; // signals completion
|
|
|
|
*prev = (*prev)->next; // unlink
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_cond_broadcast(&wakeup);
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
prev = &(*prev)->next;
|
|
|
|
}
|
|
|
|
*prev = &wait;
|
|
|
|
while (wait.value)
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_cond_wait(&wakeup, &lock);
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
done:
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&lock);
|
Add Plan 9-style barriers
Plan 9 has a very interesting synchronization mechanism, the
rendezvous() call. A good property of this is that you don't need to
explicitly initialize and destroy a barrier object, unlike as with e.g.
POSIX barriers (which are mandatory to begin with). Upon "meeting", they
can exchange a value.
This mechanism will be nice to synchronize certain stages of
initialization between threads in the following commit.
Unlike Plan 9 rendezvous(), this is not implemented with a hashtable,
because that would require additional effort (especially if you want to
make it actually scele). Unlike the Plan 9 variant, we use intptr_t
instead of void* as type for the value, because I expect that we will be
mostly passing a status code as value and not a pointer. Converting an
integer to void* requires two cast (because the integer needs to be
intptr_t), the other way around it's only one cast.
We don't particularly care about performance in this case either. It's
simply not important for our use-case. So a simple linked list is used
for waiters, and on wakeup, all waiters are temporarily woken up.
2014-07-26 18:29:48 +00:00
|
|
|
return value;
|
|
|
|
}
|