ecl/src/c/threads/mailbox.d
Marius Gerbershagen 23a7ade20c multithreading: implement mailboxes using native mutexes
The old implementation was not race condition free. If two threads (A
and B) were writing at the same time while one thread (C) was reading,
the following could happen:

1. thread A increases the write pointer (but does not store the
   message yet)
2. thread B increases the write pointer, stores the message and
   signals thread C
3. thread C tries to read from the location that thread A has not yet
   written to

The new implementation is a simple and obvious solution using a common
mutex and two condition variables for reading/writing. We don't bother
with a (complex) interrupt safe implementation.
2021-08-29 17:23:20 +02:00

180 lines
5.1 KiB
C
Executable file

/* -*- Mode: C; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/* vim: set filetype=c tabstop=2 shiftwidth=2 expandtab: */
/*
* mailbox.d -- thread communication queue
*
* Copyright (c) 2012 Juan Jose Garcia Ripoll
*
* See file 'LICENSE' for the copyright details.
*
*/
#include <ecl/ecl.h>
#include <ecl/internal.h>
/* NOTE: The mailbox functions are not interrupt safe. */
cl_object
ecl_make_mailbox(cl_object name, cl_fixnum count)
{
cl_env_ptr env = ecl_process_env();
cl_object output = ecl_alloc_object(t_mailbox);
output->mailbox.name = name;
output->mailbox.data = si_make_vector(ECL_T, /* element type */
ecl_make_fixnum(count), /* size */
ECL_NIL, /* adjustable */
ECL_NIL, /* fill pointer */
ECL_NIL, /* displaced to */
ECL_NIL); /* displacement */
output->mailbox.message_count = 0;
output->mailbox.read_pointer = 0;
output->mailbox.write_pointer = 0;
ecl_disable_interrupts_env(env);
ecl_mutex_init(&output->mailbox.mutex, FALSE);
ecl_cond_var_init(&output->mailbox.reader_cv);
ecl_cond_var_init(&output->mailbox.writer_cv);
ecl_set_finalizer_unprotected(output, ECL_T);
ecl_enable_interrupts_env(env);
return output;
}
@(defun mp::make-mailbox (&key name (count ecl_make_fixnum(128)))
@ {
@(return ecl_make_mailbox(name, fixnnint(count)));
} @)
cl_object
mp_mailbox_name(cl_object mailbox)
{
cl_env_ptr env = ecl_process_env();
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-name], mailbox, @[mp::mailbox]);
}
ecl_return1(env, mailbox->mailbox.name);
}
cl_object
mp_mailbox_count(cl_object mailbox)
{
cl_env_ptr env = ecl_process_env();
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-count], mailbox, @[mp::mailbox]);
}
ecl_return1(env, ecl_make_fixnum(mailbox->mailbox.data->vector.dim));
}
cl_object
mp_mailbox_empty_p(cl_object mailbox)
{
cl_env_ptr env = ecl_process_env();
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-empty-p], mailbox, @[mp::mailbox]);
}
ecl_return1(env, mailbox->mailbox.message_count? ECL_NIL : ECL_T);
}
static cl_object
read_message(cl_object mailbox)
{
cl_object output;
cl_fixnum ndx = mailbox->mailbox.read_pointer++;
if (mailbox->mailbox.read_pointer >= mailbox->mailbox.data->vector.dim) {
mailbox->mailbox.read_pointer = 0;
}
output = mailbox->mailbox.data->vector.self.t[ndx];
mailbox->mailbox.message_count--;
ecl_cond_var_signal(&mailbox->mailbox.writer_cv);
return output;
}
cl_object
mp_mailbox_read(cl_object mailbox)
{
cl_env_ptr env = ecl_process_env();
cl_object output;
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-read], mailbox, @[mp::mailbox]);
}
ecl_mutex_lock(&mailbox->mailbox.mutex);
{
while (mailbox->mailbox.message_count == 0) {
ecl_cond_var_wait(&mailbox->mailbox.reader_cv, &mailbox->mailbox.mutex);
}
output = read_message(mailbox);
}
ecl_mutex_unlock(&mailbox->mailbox.mutex);
ecl_return1(env, output);
}
cl_object
mp_mailbox_try_read(cl_object mailbox)
{
cl_env_ptr env = ecl_process_env();
cl_object output;
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-try-read], mailbox, @[mp::mailbox]);
}
ecl_mutex_lock(&mailbox->mailbox.mutex);
{
if (mailbox->mailbox.message_count == 0) {
output = ECL_NIL;
} else {
output = read_message(mailbox);
}
}
ecl_mutex_unlock(&mailbox->mailbox.mutex);
ecl_return1(env, output);
}
static void
store_message(cl_object mailbox, cl_object msg)
{
cl_fixnum ndx = mailbox->mailbox.write_pointer++;
if (mailbox->mailbox.write_pointer >= mailbox->mailbox.data->vector.dim) {
mailbox->mailbox.write_pointer = 0;
}
mailbox->mailbox.data->vector.self.t[ndx] = msg;
mailbox->mailbox.message_count++;
ecl_cond_var_signal(&mailbox->mailbox.reader_cv);
}
cl_object
mp_mailbox_send(cl_object mailbox, cl_object msg)
{
cl_env_ptr env = ecl_process_env();
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-send], mailbox, @[mp::mailbox]);
}
ecl_mutex_lock(&mailbox->mailbox.mutex);
{
while (mailbox->mailbox.message_count == mailbox->mailbox.data->vector.dim) {
ecl_cond_var_wait(&mailbox->mailbox.writer_cv, &mailbox->mailbox.mutex);
}
store_message(mailbox, msg);
}
ecl_mutex_unlock(&mailbox->mailbox.mutex);
ecl_return1(env, msg);
}
cl_object
mp_mailbox_try_send(cl_object mailbox, cl_object msg)
{
cl_env_ptr env = ecl_process_env();
cl_object output;
unlikely_if (ecl_t_of(mailbox) != t_mailbox) {
FEwrong_type_only_arg(@[mp::mailbox-try-send], mailbox, @[mp::mailbox]);
}
ecl_mutex_lock(&mailbox->mailbox.mutex);
{
if (mailbox->mailbox.message_count == mailbox->mailbox.data->vector.dim) {
output = ECL_NIL;
} else {
store_message(mailbox, msg);
output = msg;
}
}
ecl_mutex_unlock(&mailbox->mailbox.mutex);
ecl_return1(env, output);
}