mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-17 18:33:58 +00:00
532 lines
15 KiB
Mathematica
532 lines
15 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 2000-2001,2003-2004, 2006-2007, 2009-2011 The University of Melbourne.
|
|
% Copyright (C) 2014-2018, 2020-2022 The Mercury Team.
|
|
% This file is distributed under the terms specified in COPYING.LIB.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: thread.semaphore.m.
|
|
% Main author: conway.
|
|
% Stability: medium.
|
|
%
|
|
% This module implements a simple semaphore data type for allowing
|
|
% threads to synchronise with one another.
|
|
%
|
|
% The operations in this module are no-ops in the hlc grades that do not
|
|
% contain a .par component.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module thread.semaphore.
|
|
:- interface.
|
|
|
|
:- import_module bool.
|
|
:- import_module io.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type semaphore.
|
|
|
|
% init(Count, Sem, !IO) creates a new semaphore Sem with its counter
|
|
% initialized to Count.
|
|
%
|
|
:- pred init(int::in, semaphore::uo, io::di, io::uo) is det.
|
|
|
|
% init(Sem, !IO) creates a new semaphore Sem with its counter
|
|
% initialized to 0.
|
|
%
|
|
:- pred init(semaphore::uo, io::di, io::uo) is det.
|
|
|
|
% Sem = init(Count) returns a new semaphore Sem with its counter
|
|
% initialized to Count.
|
|
%
|
|
:- impure func impure_init(int::in) = (semaphore::uo) is det.
|
|
|
|
% signal(Sem, !IO) increments the counter associated with Sem
|
|
% and if the resulting counter has a value greater than 0, it wakes
|
|
% one or more threads that are waiting on this semaphore (if
|
|
% any).
|
|
%
|
|
:- pred signal(semaphore::in, io::di, io::uo) is det.
|
|
|
|
% wait(Sem, !IO) blocks until the counter associated with Sem
|
|
% becomes greater than 0, whereupon it wakes, decrements the
|
|
% counter and returns.
|
|
%
|
|
:- pred wait(semaphore::in, io::di, io::uo) is det.
|
|
|
|
% try_wait(Sem, Succ, !IO) is the same as wait/3, except that
|
|
% instead of blocking, it binds Succ to a boolean indicating
|
|
% whether the call succeeded in obtaining the semaphore or not.
|
|
%
|
|
:- pred try_wait(semaphore::in, bool::out, io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
init(Count, Semaphore, !IO) :-
|
|
promise_pure (
|
|
impure impure_init(Count, Semaphore)
|
|
).
|
|
|
|
init(Semaphore, !IO) :-
|
|
init(0, Semaphore, !IO).
|
|
|
|
impure_init(Count) = Semaphore :-
|
|
impure impure_init(Count, Semaphore).
|
|
|
|
signal(Semaphore, !IO) :-
|
|
promise_pure (
|
|
impure impure_signal(Semaphore),
|
|
!:IO = !.IO
|
|
).
|
|
|
|
wait(Semaphore, !IO) :-
|
|
promise_pure (
|
|
impure impure_wait(Semaphore),
|
|
!:IO = !.IO
|
|
).
|
|
|
|
try_wait(Sem, Res, !IO) :-
|
|
promise_pure (
|
|
impure impure_try_wait(Sem, Res)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
:- interface.
|
|
|
|
% The semaphore operations above can be used without the I/O state in impure
|
|
% code. These predicates are provided for use by implementors.
|
|
|
|
:- impure pred impure_init(int::in, semaphore::uo) is det.
|
|
|
|
:- impure pred impure_init(semaphore::uo) is det.
|
|
|
|
:- impure pred impure_wait(semaphore::in) is det.
|
|
|
|
:- impure pred impure_try_wait(semaphore::in, bool::out) is det.
|
|
|
|
:- impure pred impure_signal(semaphore::in) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
:- implementation.
|
|
|
|
:- pragma foreign_decl("C", "
|
|
#include <stdio.h>
|
|
#include ""mercury_context.h""
|
|
#include ""mercury_thread.h""
|
|
|
|
typedef struct ML_SEMAPHORE_STRUCT {
|
|
MR_Integer count;
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
MR_Context *suspended_head;
|
|
MR_Context *suspended_tail;
|
|
#else
|
|
#ifdef MR_THREAD_SAFE
|
|
MercuryCond cond;
|
|
#endif
|
|
#endif
|
|
#ifdef MR_THREAD_SAFE
|
|
MercuryLock lock;
|
|
#endif
|
|
} ML_Semaphore;
|
|
").
|
|
|
|
:- pragma foreign_code("C#", "
|
|
public class ML_Semaphore {
|
|
public int count;
|
|
};
|
|
").
|
|
|
|
% XXX the struct tag works around bug #19 in high-level C grades
|
|
:- pragma foreign_type("C", semaphore, "struct ML_SEMAPHORE_STRUCT *",
|
|
[can_pass_as_mercury_type]).
|
|
:- pragma foreign_type("C#", semaphore, "thread__semaphore.ML_Semaphore").
|
|
:- pragma foreign_type("Java", semaphore, "jmercury.runtime.Semaphore").
|
|
|
|
:- pragma foreign_decl("C", "
|
|
extern void
|
|
ML_finalize_semaphore(void *obj, void *cd);
|
|
").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
impure_init(Semaphore) :-
|
|
impure impure_init(0, Semaphore).
|
|
|
|
:- pragma foreign_proc("C",
|
|
impure_init(Count::in, Semaphore::uo),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
ML_Semaphore *sem;
|
|
|
|
MR_incr_hp_type_msg(sem, ML_Semaphore,
|
|
MR_ALLOC_ID, ""thread.semaphore.semaphore/0"");
|
|
sem->count = Count;
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
sem->suspended_head = NULL;
|
|
sem->suspended_tail = NULL;
|
|
#else
|
|
#ifdef MR_THREAD_SAFE
|
|
pthread_cond_init(&(sem->cond), MR_COND_ATTR);
|
|
#endif
|
|
#endif
|
|
#ifdef MR_THREAD_SAFE
|
|
pthread_mutex_init(&(sem->lock), MR_MUTEX_ATTR);
|
|
#endif
|
|
|
|
// The condvar and the mutex will need to be destroyed
|
|
// when the semaphore is garbage collected.
|
|
MR_GC_register_finalizer(sem, ML_finalize_semaphore, NULL);
|
|
|
|
Semaphore = sem;
|
|
").
|
|
|
|
:- pragma foreign_proc("C#",
|
|
impure_init(Count::in, Semaphore::uo),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
Semaphore = new thread__semaphore.ML_Semaphore();
|
|
Semaphore.count = Count;
|
|
").
|
|
|
|
:- pragma foreign_proc("Java",
|
|
impure_init(Count::in, Semaphore::uo),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
Semaphore = new jmercury.runtime.Semaphore(Count);
|
|
").
|
|
|
|
:- pragma foreign_code("C", "
|
|
|
|
void
|
|
ML_finalize_semaphore(void *obj, void *cd)
|
|
{
|
|
ML_Semaphore *sem;
|
|
|
|
sem = (ML_Semaphore *) obj;
|
|
|
|
#if defined(MR_THREAD_SAFE)
|
|
#if defined(MR_HIGHLEVEL_CODE)
|
|
pthread_cond_destroy(&(sem->cond));
|
|
#endif
|
|
pthread_mutex_destroy(&(sem->lock));
|
|
#endif
|
|
}
|
|
").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% impure_signal causes the calling context to resume in semaphore.nop,
|
|
% which simply jumps to the succip. That will return control to the caller
|
|
% of semaphore.signal as intended, but not if this procedure is inlined.
|
|
%
|
|
% XXX get rid of this limitation at some stage.
|
|
%
|
|
:- pragma no_inline(pred(semaphore.impure_signal/1)).
|
|
:- pragma foreign_proc("C",
|
|
impure_signal(Semaphore::in),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
ML_Semaphore *sem;
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
MR_Context *ctxt;
|
|
#endif
|
|
|
|
sem = (ML_Semaphore *) Semaphore;
|
|
|
|
MR_LOCK(&(sem->lock), ""semaphore.signal"");
|
|
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
if (sem->count >= 0 && sem->suspended_head != NULL) {
|
|
// Reschedule the context at the start of the queue.
|
|
ctxt = sem->suspended_head;
|
|
sem->suspended_head = ctxt->MR_ctxt_next;
|
|
if (sem->suspended_tail == ctxt) {
|
|
sem->suspended_tail = ctxt->MR_ctxt_next;
|
|
assert(sem->suspended_tail == NULL);
|
|
}
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.signal"");
|
|
MR_schedule_context(ctxt);
|
|
|
|
// yield()
|
|
// This context switch can be done more directly and faster.
|
|
MR_save_context(MR_ENGINE(MR_eng_this_context));
|
|
#ifdef ML_THREAD_AVOID_LABEL_ADDRS
|
|
MR_ENGINE(MR_eng_this_context)->MR_ctxt_resume =
|
|
MR_ENTRY(mercury__thread__semaphore__nop);
|
|
#else
|
|
MR_ENGINE(MR_eng_this_context)->MR_ctxt_resume =
|
|
&&signal_skip_to_the_end_1;
|
|
#endif
|
|
MR_schedule_context(MR_ENGINE(MR_eng_this_context));
|
|
|
|
MR_ENGINE(MR_eng_this_context) = NULL;
|
|
MR_idle();
|
|
|
|
#ifndef ML_THREAD_AVOID_LABEL_ADDRS
|
|
signal_skip_to_the_end_1: ;
|
|
#endif
|
|
} else {
|
|
sem->count++;
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.signal"");
|
|
|
|
// yield()
|
|
MR_save_context(MR_ENGINE(MR_eng_this_context));
|
|
#ifdef ML_THREAD_AVOID_LABEL_ADDRS
|
|
MR_ENGINE(MR_eng_this_context)->MR_ctxt_resume =
|
|
MR_ENTRY(mercury__thread__semaphore__nop);
|
|
#else
|
|
MR_ENGINE(MR_eng_this_context)->MR_ctxt_resume =
|
|
&&signal_skip_to_the_end_2;
|
|
#endif
|
|
MR_schedule_context(MR_ENGINE(MR_eng_this_context));
|
|
|
|
MR_ENGINE(MR_eng_this_context) = NULL;
|
|
MR_idle();
|
|
|
|
#ifndef ML_THREAD_AVOID_LABEL_ADDRS
|
|
signal_skip_to_the_end_2: ;
|
|
#endif
|
|
}
|
|
#else
|
|
sem->count++;
|
|
MR_COND_SIGNAL(&(sem->cond), ""semaphore.signal"");
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.signal"");
|
|
#endif
|
|
").
|
|
|
|
:- pragma foreign_proc("C#",
|
|
impure_signal(Semaphore::in),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
System.Threading.Monitor.Enter(Semaphore);
|
|
Semaphore.count++;
|
|
// XXX I think we only need to do a Pulse.
|
|
System.Threading.Monitor.PulseAll(Semaphore);
|
|
System.Threading.Monitor.Exit(Semaphore);
|
|
").
|
|
|
|
:- pragma foreign_proc("Java",
|
|
impure_signal(Semaphore::in),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
Semaphore.release();
|
|
").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% impure_wait causes the calling context to resume in semaphore.nop,
|
|
% which simply jumps to the succip. That will return control to the caller
|
|
% of semaphore.wait as intended, but not if this procedure is inlined.
|
|
%
|
|
% XXX get rid of this limitation at some stage.
|
|
%
|
|
:- pragma no_inline(pred(impure_wait/1)).
|
|
:- pragma foreign_proc("C",
|
|
impure_wait(Semaphore::in),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
ML_Semaphore *sem;
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
MR_Context *ctxt;
|
|
#endif
|
|
|
|
sem = (ML_Semaphore *) Semaphore;
|
|
|
|
MR_LOCK(&(sem->lock), ""semaphore.wait"");
|
|
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
if (sem->count > 0) {
|
|
sem->count--;
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.wait"");
|
|
} else {
|
|
MR_save_context(MR_ENGINE(MR_eng_this_context));
|
|
|
|
// Put the current context at the end of the queue.
|
|
ctxt = MR_ENGINE(MR_eng_this_context);
|
|
#ifdef ML_THREAD_AVOID_LABEL_ADDRS
|
|
ctxt->MR_ctxt_resume = MR_ENTRY(mercury__thread__semaphore__nop);
|
|
#else
|
|
ctxt->MR_ctxt_resume = &&wait_skip_to_the_end;
|
|
#endif
|
|
ctxt->MR_ctxt_next = NULL;
|
|
if (sem->suspended_tail) {
|
|
sem->suspended_tail->MR_ctxt_next = ctxt;
|
|
sem->suspended_tail = ctxt;
|
|
} else {
|
|
sem->suspended_head = ctxt;
|
|
sem->suspended_tail = ctxt;
|
|
}
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.wait"");
|
|
|
|
// Make the current engine do something else.
|
|
MR_ENGINE(MR_eng_this_context) = NULL;
|
|
MR_idle();
|
|
|
|
#ifndef ML_THREAD_AVOID_LABEL_ADDRS
|
|
wait_skip_to_the_end: ;
|
|
#endif
|
|
}
|
|
#else
|
|
while (sem->count <= 0) {
|
|
// Although it goes against the spec, pthread_cond_wait() can return
|
|
// prematurely with the error code EINTR in glibc 2.3.2
|
|
// if the thread is sent a signal.
|
|
|
|
while (MR_COND_WAIT(&(sem->cond), &(sem->lock), ""semaphore.wait"")
|
|
!= 0)
|
|
{
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
sem->count--;
|
|
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.wait"");
|
|
#endif
|
|
").
|
|
|
|
:- pragma foreign_proc("C#",
|
|
impure_wait(Semaphore::in),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
System.Threading.Monitor.Enter(Semaphore);
|
|
|
|
while (Semaphore.count <= 0) {
|
|
System.Threading.Monitor.Wait(Semaphore);
|
|
}
|
|
|
|
Semaphore.count--;
|
|
|
|
System.Threading.Monitor.Exit(Semaphore);
|
|
").
|
|
|
|
:- pragma foreign_proc("Java",
|
|
impure_wait(Semaphore::in),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
// acquire() might be useful as well; it will throw an exception
|
|
// if the thread is interrupted.
|
|
Semaphore.acquireUninterruptibly();
|
|
").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
impure_try_wait(Sem, Res) :-
|
|
impure impure_try_wait_2(Sem, Res0),
|
|
Res = ( if Res0 = 0 then yes else no ).
|
|
|
|
:- impure pred impure_try_wait_2(semaphore::in, int::out) is det.
|
|
|
|
:- pragma foreign_proc("C",
|
|
impure_try_wait_2(Semaphore::in, Res::out),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
ML_Semaphore *sem;
|
|
|
|
sem = (ML_Semaphore *) Semaphore;
|
|
|
|
MR_LOCK(&(sem->lock), ""semaphore.try_wait"");
|
|
if (sem->count > 0) {
|
|
sem->count--;
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.try_wait"");
|
|
Res = 0;
|
|
} else {
|
|
MR_UNLOCK(&(sem->lock), ""semaphore.try_wait"");
|
|
Res = 1;
|
|
}
|
|
").
|
|
|
|
:- pragma foreign_proc("C#",
|
|
impure_try_wait_2(Semaphore::in, Res::out),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
if (System.Threading.Monitor.TryEnter(Semaphore)) {
|
|
if (Semaphore.count > 0) {
|
|
Semaphore.count--;
|
|
System.Threading.Monitor.Exit(Semaphore);
|
|
Res = 0;
|
|
} else {
|
|
System.Threading.Monitor.Exit(Semaphore);
|
|
Res = 1;
|
|
}
|
|
} else {
|
|
Res = 1;
|
|
}
|
|
").
|
|
|
|
:- pragma foreign_proc("Java",
|
|
impure_try_wait_2(Semaphore::in, Res::out),
|
|
[will_not_call_mercury, thread_safe],
|
|
"
|
|
Res = Semaphore.tryAcquire() ? 0 : 1;
|
|
").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pragma foreign_decl("C",
|
|
"
|
|
/*
|
|
INIT mercury_sys_init_semaphore_modules
|
|
*/
|
|
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
MR_define_extern_entry(mercury__thread__semaphore__nop);
|
|
#endif
|
|
").
|
|
|
|
:- pragma foreign_code("C",
|
|
"
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
|
|
MR_BEGIN_MODULE(hand_written_semaphores_module)
|
|
MR_init_entry_ai(mercury__thread__semaphore__nop);
|
|
MR_BEGIN_CODE
|
|
|
|
MR_define_entry(mercury__thread__semaphore__nop);
|
|
{
|
|
MR_proceed();
|
|
}
|
|
MR_END_MODULE
|
|
|
|
#endif
|
|
|
|
// Forward decls to suppress gcc warnings.
|
|
void mercury_sys_init_semaphore_modules_init(void);
|
|
void mercury_sys_init_semaphore_modules_init_type_tables(void);
|
|
#ifdef MR_DEEP_PROFILING
|
|
void mercury_sys_init_semaphore_modules_write_out_proc_statics(
|
|
FILE *deep_fp, FILE *procrep_fp);
|
|
#endif
|
|
|
|
void mercury_sys_init_semaphore_modules_init(void)
|
|
{
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
hand_written_semaphores_module();
|
|
#endif
|
|
}
|
|
|
|
void mercury_sys_init_semaphore_modules_init_type_tables(void)
|
|
{
|
|
// No types to register.
|
|
}
|
|
|
|
#ifdef MR_DEEP_PROFILING
|
|
void mercury_sys_init_semaphore_modules_write_out_proc_statics(
|
|
FILE *deep_fp, FILE *procrep_fp)
|
|
{
|
|
// No proc_statics to write out.
|
|
}
|
|
#endif
|
|
").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module thread.semaphore.
|
|
%---------------------------------------------------------------------------%
|