mirror of
https://github.com/ruby/ruby.git
synced 2026-08-01 02:20:48 +08:00
Make Monitor a core class
[Feature #21788] It allows monitor to access internal routines and remove some overhead. Before: ``` ruby 4.0.0dev (2025-12-13T04:52:13Z master 71dd272506) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- Mutex 2.111M i/100ms Monitor 1.736M i/100ms Calculating ------------------------------------- Mutex 25.050M (± 0.4%) i/s (39.92 ns/i) - 126.631M in 5.055208s Monitor 19.809M (± 0.1%) i/s (50.48 ns/i) - 100.672M in 5.082015s ``` After: ``` ruby 4.0.0dev (2025-12-13T06:49:18Z core-monitor 6fabf389fd) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- Mutex 2.144M i/100ms Monitor 1.859M i/100ms Calculating ------------------------------------- Mutex 24.771M (± 0.4%) i/s (40.37 ns/i) - 124.342M in 5.019716s Monitor 23.722M (± 0.4%) i/s (42.15 ns/i) - 118.998M in 5.016361s ``` Bench: ```ruby require 'bundler/inline' gemfile do gem "benchmark-ips" end mutex = Mutex.new require "monitor" monitor = Monitor.new Benchmark.ips do |x| x.report("Mutex") { mutex.synchronize { } } x.report("Monitor") { monitor.synchronize { } } end ```
This commit is contained in:
parent
f33073a6cb
commit
d8b8a95af9
Notes:
git
2026-02-12 19:35:46 +00:00
@ -1,162 +0,0 @@
|
||||
# AUTOGENERATED DEPENDENCIES START
|
||||
monitor.o: $(RUBY_EXTCONF_H)
|
||||
monitor.o: $(arch_hdrdir)/ruby/config.h
|
||||
monitor.o: $(hdrdir)/ruby/assert.h
|
||||
monitor.o: $(hdrdir)/ruby/backward.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/assume.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/attributes.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/bool.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/inttypes.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/limits.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/long_long.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/stdalign.h
|
||||
monitor.o: $(hdrdir)/ruby/backward/2/stdarg.h
|
||||
monitor.o: $(hdrdir)/ruby/defines.h
|
||||
monitor.o: $(hdrdir)/ruby/intern.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/abi.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/anyargs.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/char.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/double.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/int.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/long.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/short.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/assume.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/alloc_size.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/artificial.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/cold.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/const.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/constexpr.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/deprecated.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/error.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/flag_enum.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/forceinline.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/format.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/noalias.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/nodiscard.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/noexcept.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/noinline.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/nonnull.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/noreturn.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/packed_struct.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/pure.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/restrict.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/warning.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/attr/weakref.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/cast.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is/apple.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is/clang.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is/intel.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/compiler_since.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/config.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/constant_p.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rarray.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rbasic.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rbignum.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rclass.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rdata.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rfile.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rhash.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/robject.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rregexp.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rstring.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rstruct.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/core/rtypeddata.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/ctype.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/dllexport.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/dosish.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/error.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/eval.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/event.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/fl_type.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/gc.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/glob.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/globals.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/attribute.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/builtin.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/c_attribute.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/extension.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/feature.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/has/warning.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/array.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/bignum.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/class.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/compar.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/complex.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/cont.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/dir.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/enum.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/enumerator.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/error.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/eval.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/file.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/hash.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/io.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/load.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/marshal.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/numeric.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/object.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/parse.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/proc.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/process.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/random.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/range.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/rational.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/re.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/ruby.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/select.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/set.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/signal.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/sprintf.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/string.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/struct.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/thread.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/time.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/variable.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/intern/vm.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/interpreter.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/iterator.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/memory.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/method.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/module.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/newobj.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/scan_args.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/special_consts.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/static_assert.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/stdalign.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/stdbool.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/stdckdint.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/symbol.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/value.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/value_type.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/variable.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/warning_push.h
|
||||
monitor.o: $(hdrdir)/ruby/internal/xmalloc.h
|
||||
monitor.o: $(hdrdir)/ruby/missing.h
|
||||
monitor.o: $(hdrdir)/ruby/ruby.h
|
||||
monitor.o: $(hdrdir)/ruby/st.h
|
||||
monitor.o: $(hdrdir)/ruby/subst.h
|
||||
monitor.o: monitor.c
|
||||
# AUTOGENERATED DEPENDENCIES END
|
||||
@ -1,2 +0,0 @@
|
||||
require 'mkmf'
|
||||
create_makefile('monitor')
|
||||
@ -1,301 +0,0 @@
|
||||
#include "ruby/ruby.h"
|
||||
|
||||
/* Thread::Monitor */
|
||||
|
||||
struct rb_monitor {
|
||||
long count;
|
||||
VALUE owner;
|
||||
VALUE mutex;
|
||||
};
|
||||
|
||||
static void
|
||||
monitor_mark(void *ptr)
|
||||
{
|
||||
struct rb_monitor *mc = ptr;
|
||||
rb_gc_mark_movable(mc->owner);
|
||||
rb_gc_mark_movable(mc->mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_compact(void *ptr)
|
||||
{
|
||||
struct rb_monitor *mc = ptr;
|
||||
mc->owner = rb_gc_location(mc->owner);
|
||||
mc->mutex = rb_gc_location(mc->mutex);
|
||||
}
|
||||
|
||||
static const rb_data_type_t monitor_data_type = {
|
||||
.wrap_struct_name = "monitor",
|
||||
.function = {
|
||||
.dmark = monitor_mark,
|
||||
.dfree = RUBY_TYPED_DEFAULT_FREE,
|
||||
.dsize = NULL, // Fully embeded
|
||||
.dcompact = monitor_compact,
|
||||
},
|
||||
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
|
||||
};
|
||||
|
||||
static VALUE
|
||||
monitor_alloc(VALUE klass)
|
||||
{
|
||||
struct rb_monitor *mc;
|
||||
VALUE obj;
|
||||
|
||||
obj = TypedData_Make_Struct(klass, struct rb_monitor, &monitor_data_type, mc);
|
||||
RB_OBJ_WRITE(obj, &mc->mutex, rb_mutex_new());
|
||||
RB_OBJ_WRITE(obj, &mc->owner, Qnil);
|
||||
mc->count = 0;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static struct rb_monitor *
|
||||
monitor_ptr(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc;
|
||||
TypedData_Get_Struct(monitor, struct rb_monitor, &monitor_data_type, mc);
|
||||
return mc;
|
||||
}
|
||||
|
||||
static bool
|
||||
mc_owner_p(struct rb_monitor *mc, VALUE current_fiber)
|
||||
{
|
||||
return mc->owner == current_fiber;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* try_enter -> true or false
|
||||
*
|
||||
* Attempts to enter exclusive section. Returns +false+ if lock fails.
|
||||
*/
|
||||
static VALUE
|
||||
monitor_try_enter(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
|
||||
VALUE current_fiber = rb_fiber_current();
|
||||
if (!mc_owner_p(mc, current_fiber)) {
|
||||
if (!rb_mutex_trylock(mc->mutex)) {
|
||||
return Qfalse;
|
||||
}
|
||||
RB_OBJ_WRITE(monitor, &mc->owner, current_fiber);
|
||||
mc->count = 0;
|
||||
}
|
||||
mc->count += 1;
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
|
||||
struct monitor_args {
|
||||
VALUE monitor;
|
||||
struct rb_monitor *mc;
|
||||
VALUE current_fiber;
|
||||
};
|
||||
|
||||
static inline void
|
||||
monitor_args_init(struct monitor_args *args, VALUE monitor)
|
||||
{
|
||||
args->monitor = monitor;
|
||||
args->mc = monitor_ptr(monitor);
|
||||
args->current_fiber = rb_fiber_current();
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_enter0(struct monitor_args *args)
|
||||
{
|
||||
if (!mc_owner_p(args->mc, args->current_fiber)) {
|
||||
rb_mutex_lock(args->mc->mutex);
|
||||
RB_OBJ_WRITE(args->monitor, &args->mc->owner, args->current_fiber);
|
||||
args->mc->count = 0;
|
||||
}
|
||||
args->mc->count++;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* enter -> nil
|
||||
*
|
||||
* Enters exclusive section.
|
||||
*/
|
||||
static VALUE
|
||||
monitor_enter(VALUE monitor)
|
||||
{
|
||||
struct monitor_args args;
|
||||
monitor_args_init(&args, monitor);
|
||||
monitor_enter0(&args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static inline void
|
||||
monitor_check_owner0(struct monitor_args *args)
|
||||
{
|
||||
if (!mc_owner_p(args->mc, args->current_fiber)) {
|
||||
rb_raise(rb_eThreadError, "current fiber not owner");
|
||||
}
|
||||
}
|
||||
|
||||
/* :nodoc: */
|
||||
static VALUE
|
||||
monitor_check_owner(VALUE monitor)
|
||||
{
|
||||
struct monitor_args args;
|
||||
monitor_args_init(&args, monitor);
|
||||
monitor_check_owner0(&args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_exit0(struct monitor_args *args)
|
||||
{
|
||||
monitor_check_owner0(args);
|
||||
|
||||
if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count);
|
||||
args->mc->count--;
|
||||
|
||||
if (args->mc->count == 0) {
|
||||
RB_OBJ_WRITE(args->monitor, &args->mc->owner, Qnil);
|
||||
rb_mutex_unlock(args->mc->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* exit -> nil
|
||||
*
|
||||
* Leaves exclusive section.
|
||||
*/
|
||||
static VALUE
|
||||
monitor_exit(VALUE monitor)
|
||||
{
|
||||
struct monitor_args args;
|
||||
monitor_args_init(&args, monitor);
|
||||
monitor_exit0(&args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
/* :nodoc: */
|
||||
static VALUE
|
||||
monitor_locked_p(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
return rb_mutex_locked_p(mc->mutex);
|
||||
}
|
||||
|
||||
/* :nodoc: */
|
||||
static VALUE
|
||||
monitor_owned_p(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
return rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_fiber_current()) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_exit_for_cond(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
long cnt = mc->count;
|
||||
RB_OBJ_WRITE(monitor, &mc->owner, Qnil);
|
||||
mc->count = 0;
|
||||
return LONG2NUM(cnt);
|
||||
}
|
||||
|
||||
struct wait_for_cond_data {
|
||||
VALUE monitor;
|
||||
VALUE cond;
|
||||
VALUE timeout;
|
||||
VALUE count;
|
||||
};
|
||||
|
||||
static VALUE
|
||||
monitor_wait_for_cond_body(VALUE v)
|
||||
{
|
||||
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
|
||||
struct rb_monitor *mc = monitor_ptr(data->monitor);
|
||||
// cond.wait(monitor.mutex, timeout)
|
||||
VALUE signaled = rb_funcall(data->cond, rb_intern("wait"), 2, mc->mutex, data->timeout);
|
||||
return RTEST(signaled) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_enter_for_cond(VALUE v)
|
||||
{
|
||||
// assert(rb_mutex_owned_p(mc->mutex) == Qtrue)
|
||||
// but rb_mutex_owned_p is not exported...
|
||||
|
||||
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
|
||||
struct rb_monitor *mc = monitor_ptr(data->monitor);
|
||||
RB_OBJ_WRITE(data->monitor, &mc->owner, rb_fiber_current());
|
||||
mc->count = NUM2LONG(data->count);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
/* :nodoc: */
|
||||
static VALUE
|
||||
monitor_wait_for_cond(VALUE monitor, VALUE cond, VALUE timeout)
|
||||
{
|
||||
VALUE count = monitor_exit_for_cond(monitor);
|
||||
struct wait_for_cond_data data = {
|
||||
monitor,
|
||||
cond,
|
||||
timeout,
|
||||
count,
|
||||
};
|
||||
|
||||
return rb_ensure(monitor_wait_for_cond_body, (VALUE)&data,
|
||||
monitor_enter_for_cond, (VALUE)&data);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_sync_body(VALUE monitor)
|
||||
{
|
||||
return rb_yield_values(0);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_sync_ensure(VALUE v_args)
|
||||
{
|
||||
monitor_exit0((struct monitor_args *)v_args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* synchronize { } -> result of the block
|
||||
*
|
||||
* Enters exclusive section and executes the block. Leaves the exclusive
|
||||
* section automatically when the block exits. See example under
|
||||
* +MonitorMixin+.
|
||||
*/
|
||||
static VALUE
|
||||
monitor_synchronize(VALUE monitor)
|
||||
{
|
||||
struct monitor_args args;
|
||||
monitor_args_init(&args, monitor);
|
||||
monitor_enter0(&args);
|
||||
return rb_ensure(monitor_sync_body, (VALUE)&args, monitor_sync_ensure, (VALUE)&args);
|
||||
}
|
||||
|
||||
void
|
||||
Init_monitor(void)
|
||||
{
|
||||
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
||||
rb_ext_ractor_safe(true);
|
||||
#endif
|
||||
|
||||
VALUE rb_cMonitor = rb_define_class("Monitor", rb_cObject);
|
||||
rb_define_alloc_func(rb_cMonitor, monitor_alloc);
|
||||
|
||||
rb_define_method(rb_cMonitor, "try_enter", monitor_try_enter, 0);
|
||||
rb_define_method(rb_cMonitor, "enter", monitor_enter, 0);
|
||||
rb_define_method(rb_cMonitor, "exit", monitor_exit, 0);
|
||||
rb_define_method(rb_cMonitor, "synchronize", monitor_synchronize, 0);
|
||||
|
||||
/* internal methods for MonitorMixin */
|
||||
rb_define_method(rb_cMonitor, "mon_locked?", monitor_locked_p, 0);
|
||||
rb_define_method(rb_cMonitor, "mon_check_owner", monitor_check_owner, 0);
|
||||
rb_define_method(rb_cMonitor, "mon_owned?", monitor_owned_p, 0);
|
||||
|
||||
/* internal methods for MonitorMixin::ConditionVariable */
|
||||
rb_define_method(rb_cMonitor, "wait_for_cond", monitor_wait_for_cond, 2);
|
||||
}
|
||||
@ -6,9 +6,6 @@
|
||||
# This library is distributed under the terms of the Ruby license.
|
||||
# You can freely distribute/modify this library.
|
||||
#
|
||||
|
||||
require 'monitor.so'
|
||||
|
||||
#
|
||||
# In concurrent programming, a monitor is an object or module intended to be
|
||||
# used safely by more than one thread. The defining characteristic of a
|
||||
@ -89,65 +86,14 @@ require 'monitor.so'
|
||||
# MonitorMixin module.
|
||||
#
|
||||
module MonitorMixin
|
||||
ConditionVariable = Monitor::ConditionVariable # :nodoc:
|
||||
|
||||
#
|
||||
# FIXME: This isn't documented in Nutshell.
|
||||
#
|
||||
# Since MonitorMixin.new_cond returns a ConditionVariable, and the example
|
||||
# above calls while_wait and signal, this class should be documented.
|
||||
#
|
||||
class ConditionVariable
|
||||
#
|
||||
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
|
||||
#
|
||||
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
|
||||
# even if no other thread doesn't signal.
|
||||
#
|
||||
def wait(timeout = nil)
|
||||
@monitor.mon_check_owner
|
||||
@monitor.wait_for_cond(@cond, timeout)
|
||||
end
|
||||
|
||||
#
|
||||
# Calls wait repeatedly while the given block yields a truthy value.
|
||||
#
|
||||
def wait_while
|
||||
while yield
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Calls wait repeatedly until the given block yields a truthy value.
|
||||
#
|
||||
def wait_until
|
||||
until yield
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Wakes up the first thread in line waiting for this lock.
|
||||
#
|
||||
def signal
|
||||
@monitor.mon_check_owner
|
||||
@cond.signal
|
||||
end
|
||||
|
||||
#
|
||||
# Wakes up all threads waiting for this lock.
|
||||
#
|
||||
def broadcast
|
||||
@monitor.mon_check_owner
|
||||
@cond.broadcast
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(monitor) # :nodoc:
|
||||
@monitor = monitor
|
||||
@cond = Thread::ConditionVariable.new
|
||||
end
|
||||
end
|
||||
|
||||
def self.extend_object(obj) # :nodoc:
|
||||
super(obj)
|
||||
@ -245,26 +191,7 @@ module MonitorMixin
|
||||
end
|
||||
end
|
||||
|
||||
# Use the Monitor class when you want to have a lock object for blocks with
|
||||
# mutual exclusion.
|
||||
#
|
||||
# require 'monitor'
|
||||
#
|
||||
# lock = Monitor.new
|
||||
# lock.synchronize do
|
||||
# # exclusive access
|
||||
# end
|
||||
#
|
||||
class Monitor
|
||||
#
|
||||
# Creates a new MonitorMixin::ConditionVariable associated with the
|
||||
# Monitor object.
|
||||
#
|
||||
def new_cond
|
||||
::MonitorMixin::ConditionVariable.new(self)
|
||||
end
|
||||
|
||||
# for compatibility
|
||||
class Monitor # :nodoc:
|
||||
alias try_mon_enter try_enter
|
||||
alias mon_try_enter try_enter
|
||||
alias mon_enter enter
|
||||
@ -21,13 +21,16 @@ describe "Kernel#require" do
|
||||
provided << "set"
|
||||
provided << "pathname"
|
||||
end
|
||||
ruby_version_is "4.1" do
|
||||
provided << "monitor"
|
||||
end
|
||||
|
||||
it "#{provided.join(', ')} are already required" do
|
||||
out = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems --disable-did-you-mean')
|
||||
features = out.lines.map { |line| File.basename(line.chomp, '.*') }
|
||||
|
||||
# Ignore CRuby internals
|
||||
features -= %w[encdb transdb windows_1252 windows_31j]
|
||||
features -= %w[encdb transdb windows_1252 windows_31]
|
||||
features.reject! { |feature| feature.end_with?('-fake') }
|
||||
|
||||
features.sort.should == provided.sort
|
||||
@ -37,6 +40,10 @@ describe "Kernel#require" do
|
||||
requires = requires.map { |f| f == "pathname" ? "pathname.so" : f }
|
||||
end
|
||||
|
||||
ruby_version_is "4.1" do
|
||||
requires = requires.map { |f| f == "monitor" ? "monitor.so" : f }
|
||||
end
|
||||
|
||||
code = requires.map { |f| "puts require #{f.inspect}\n" }.join
|
||||
required = ruby_exe(code, options: '--disable-gems')
|
||||
required.should == "false\n" * requires.size
|
||||
|
||||
262
thread_sync.c
262
thread_sync.c
@ -1261,6 +1261,263 @@ rb_condvar_broadcast(rb_execution_context_t *ec, VALUE self)
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Thread::Monitor */
|
||||
|
||||
struct rb_monitor {
|
||||
long count;
|
||||
rb_serial_t ec_serial;
|
||||
VALUE mutex;
|
||||
};
|
||||
|
||||
static void
|
||||
monitor_mark(void *ptr)
|
||||
{
|
||||
struct rb_monitor *mc = ptr;
|
||||
rb_gc_mark_movable(mc->mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_compact(void *ptr)
|
||||
{
|
||||
struct rb_monitor *mc = ptr;
|
||||
mc->mutex = rb_gc_location(mc->mutex);
|
||||
}
|
||||
|
||||
static const rb_data_type_t monitor_data_type = {
|
||||
.wrap_struct_name = "monitor",
|
||||
.function = {
|
||||
.dmark = monitor_mark,
|
||||
.dfree = RUBY_TYPED_DEFAULT_FREE,
|
||||
.dsize = NULL, // Fully embeded
|
||||
.dcompact = monitor_compact,
|
||||
},
|
||||
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
|
||||
};
|
||||
|
||||
static VALUE
|
||||
monitor_alloc(VALUE klass)
|
||||
{
|
||||
struct rb_monitor *mc;
|
||||
VALUE obj;
|
||||
|
||||
obj = TypedData_Make_Struct(klass, struct rb_monitor, &monitor_data_type, mc);
|
||||
RB_OBJ_WRITE(obj, &mc->mutex, rb_mutex_new());
|
||||
mc->ec_serial = 0;
|
||||
mc->count = 0;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static struct rb_monitor *
|
||||
monitor_ptr(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc;
|
||||
TypedData_Get_Struct(monitor, struct rb_monitor, &monitor_data_type, mc);
|
||||
return mc;
|
||||
}
|
||||
|
||||
static bool
|
||||
mc_owner_p(struct rb_monitor *mc, rb_serial_t current_fiber_serial)
|
||||
{
|
||||
return mc->ec_serial == current_fiber_serial;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_try_enter(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
|
||||
rb_serial_t current_fiber_serial = rb_ec_serial(ec);
|
||||
if (!mc_owner_p(mc, current_fiber_serial)) {
|
||||
if (!rb_mut_trylock(ec, mc->mutex)) {
|
||||
return Qfalse;
|
||||
}
|
||||
mc->ec_serial = current_fiber_serial;
|
||||
mc->count = 0;
|
||||
}
|
||||
mc->count += 1;
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
struct monitor_args {
|
||||
VALUE monitor;
|
||||
struct rb_monitor *mc;
|
||||
rb_serial_t current_fiber_serial;
|
||||
rb_execution_context_t *ec;
|
||||
};
|
||||
|
||||
static void
|
||||
monitor_enter0(struct monitor_args *args)
|
||||
{
|
||||
if (!mc_owner_p(args->mc, args->current_fiber_serial)) {
|
||||
struct mutex_args mut_args = {
|
||||
.self = args->mc->mutex,
|
||||
.mutex = mutex_ptr(args->mc->mutex),
|
||||
.ec= args->ec,
|
||||
};
|
||||
do_mutex_lock(&mut_args, 1);
|
||||
args->mc->ec_serial = args->current_fiber_serial;
|
||||
args->mc->count = 0;
|
||||
}
|
||||
args->mc->count++;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_enter(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct monitor_args args = {
|
||||
.monitor = monitor,
|
||||
.mc = monitor_ptr(monitor),
|
||||
.ec = ec,
|
||||
.current_fiber_serial = rb_ec_serial(ec),
|
||||
};
|
||||
monitor_enter0(&args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static inline void
|
||||
monitor_check_owner0(struct monitor_args *args)
|
||||
{
|
||||
if (!mc_owner_p(args->mc, args->current_fiber_serial)) {
|
||||
rb_raise(rb_eThreadError, "current fiber not owner");
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_check_owner(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct monitor_args args = {
|
||||
.monitor = monitor,
|
||||
.mc = monitor_ptr(monitor),
|
||||
.ec = ec,
|
||||
.current_fiber_serial = rb_ec_serial(ec),
|
||||
};
|
||||
monitor_check_owner0(&args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_exit0(struct monitor_args *args)
|
||||
{
|
||||
monitor_check_owner0(args);
|
||||
|
||||
if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count);
|
||||
args->mc->count--;
|
||||
|
||||
if (args->mc->count == 0) {
|
||||
args->mc->ec_serial = 0;
|
||||
|
||||
struct mutex_args mut_args = {
|
||||
.self = args->mc->mutex,
|
||||
.mutex = mutex_ptr(args->mc->mutex),
|
||||
.ec= args->ec,
|
||||
};
|
||||
do_mutex_unlock(&mut_args);
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_exit(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct monitor_args args = {
|
||||
.monitor = monitor,
|
||||
.mc = monitor_ptr(monitor),
|
||||
.ec = ec,
|
||||
.current_fiber_serial = rb_ec_serial(ec),
|
||||
};
|
||||
monitor_exit0(&args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_locked_p(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
return rb_mutex_locked_p(mc->mutex);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_owned_p(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
return RBOOL(rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_ec_serial(ec)));
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_exit_for_cond(VALUE monitor)
|
||||
{
|
||||
struct rb_monitor *mc = monitor_ptr(monitor);
|
||||
long cnt = mc->count;
|
||||
mc->ec_serial = 0;
|
||||
mc->count = 0;
|
||||
return LONG2NUM(cnt);
|
||||
}
|
||||
|
||||
struct wait_for_cond_data {
|
||||
VALUE monitor;
|
||||
VALUE cond;
|
||||
VALUE timeout;
|
||||
VALUE count;
|
||||
};
|
||||
|
||||
static VALUE
|
||||
monitor_wait_for_cond_body(VALUE v)
|
||||
{
|
||||
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
|
||||
struct rb_monitor *mc = monitor_ptr(data->monitor);
|
||||
// cond.wait(monitor.mutex, timeout)
|
||||
VALUE signaled = rb_funcall(data->cond, rb_intern("wait"), 2, mc->mutex, data->timeout);
|
||||
return RTEST(signaled) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_enter_for_cond(VALUE v)
|
||||
{
|
||||
// assert(rb_mutex_owned_p(mc->mutex) == Qtrue)
|
||||
// but rb_mutex_owned_p is not exported...
|
||||
|
||||
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
|
||||
struct rb_monitor *mc = monitor_ptr(data->monitor);
|
||||
mc->ec_serial = rb_ec_serial(GET_EC());
|
||||
mc->count = NUM2LONG(data->count);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_wait_for_cond(rb_execution_context_t *ec, VALUE monitor, VALUE cond, VALUE timeout)
|
||||
{
|
||||
VALUE count = monitor_exit_for_cond(monitor);
|
||||
struct wait_for_cond_data data = {
|
||||
monitor,
|
||||
cond,
|
||||
timeout,
|
||||
count,
|
||||
};
|
||||
|
||||
return rb_ensure(monitor_wait_for_cond_body, (VALUE)&data,
|
||||
monitor_enter_for_cond, (VALUE)&data);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
monitor_sync_ensure(VALUE v_args)
|
||||
{
|
||||
monitor_exit0((struct monitor_args *)v_args);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_monitor_synchronize(rb_execution_context_t *ec, VALUE monitor)
|
||||
{
|
||||
struct monitor_args args = {
|
||||
.monitor = monitor,
|
||||
.mc = monitor_ptr(monitor),
|
||||
.ec = ec,
|
||||
.current_fiber_serial = rb_ec_serial(ec),
|
||||
};
|
||||
monitor_enter0(&args);
|
||||
return rb_ec_ensure(ec, do_ec_yield, (VALUE)ec, monitor_sync_ensure, (VALUE)&args);
|
||||
}
|
||||
|
||||
static void
|
||||
Init_thread_sync(void)
|
||||
{
|
||||
@ -1283,6 +1540,11 @@ Init_thread_sync(void)
|
||||
|
||||
id_sleep = rb_intern("sleep");
|
||||
|
||||
/* Monitor */
|
||||
VALUE rb_cMonitor = rb_define_class_id_under_no_pin(rb_cThread, rb_intern("Monitor"), rb_cObject);
|
||||
rb_define_alloc_func(rb_cMonitor, monitor_alloc);
|
||||
|
||||
rb_provide("monitor.so");
|
||||
rb_provide("thread.rb");
|
||||
}
|
||||
|
||||
|
||||
186
thread_sync.rb
186
thread_sync.rb
@ -529,9 +529,187 @@ class Thread
|
||||
Primitive.rb_condvar_wait(mutex, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
# Use the Monitor class when you want to have a lock object for blocks with
|
||||
# mutual exclusion.
|
||||
#
|
||||
# lock = Monitor.new
|
||||
# lock.synchronize do
|
||||
# # exclusive access
|
||||
# end
|
||||
#
|
||||
# Contrary to Mutex, Monitor is reentrant:
|
||||
#
|
||||
# lock = Monitor.new
|
||||
# lock.synchronize do
|
||||
# lock.synchronize do
|
||||
# # exclusive access
|
||||
# end
|
||||
# end
|
||||
class Monitor
|
||||
# call-seq:
|
||||
# synchronize { } -> result of the block
|
||||
#
|
||||
# Enters exclusive section and executes the block. Leaves the exclusive
|
||||
# section automatically when the block exits. See example under
|
||||
# +MonitorMixin+.
|
||||
def synchronize(&)
|
||||
Primitive.rb_monitor_synchronize
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# try_enter -> true or false
|
||||
#
|
||||
# Attempts to enter exclusive section. Returns +false+ if lock fails.
|
||||
def try_enter
|
||||
Primitive.rb_monitor_try_enter
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# enter -> nil
|
||||
#
|
||||
# Enters exclusive section.
|
||||
def enter
|
||||
Primitive.rb_monitor_enter
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# exit -> nil
|
||||
#
|
||||
# Leaves exclusive section.
|
||||
def exit
|
||||
Primitive.rb_monitor_exit
|
||||
end
|
||||
|
||||
# internal methods for MonitorMixin
|
||||
def mon_check_owner # :nodoc:
|
||||
Primitive.rb_monitor_check_owner
|
||||
end
|
||||
|
||||
def mon_locked? # :nodoc:
|
||||
Primitive.rb_monitor_locked_p
|
||||
end
|
||||
|
||||
def mon_owned? # :nodoc:
|
||||
Primitive.rb_monitor_owned_p
|
||||
end
|
||||
|
||||
# internal methods for MonitorMixin::ConditionVariable
|
||||
def wait_for_cond(cond, timeout) # :nodoc:
|
||||
Primitive.rb_monitor_wait_for_cond(cond, timeout)
|
||||
end
|
||||
|
||||
# Creates a new Monitor::ConditionVariable associated with the
|
||||
# Monitor object.
|
||||
#
|
||||
def new_cond
|
||||
ConditionVariable.new(self)
|
||||
end
|
||||
|
||||
# Condition variables, allow to suspend the current thread while in
|
||||
# the middle of a critical section until a condition is met, such as
|
||||
# a resource being available.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# monitor = Thread::Monitor.new
|
||||
#
|
||||
# resource_available = false
|
||||
# condvar = monitor.new_cond
|
||||
#
|
||||
# a1 = Thread.new {
|
||||
# # Thread 'a1' waits for the resource to become available and consumes
|
||||
# # the resource.
|
||||
# monitor.synchronize {
|
||||
# condvar.wait_until { resource_available }
|
||||
# # After the loop, 'resource_available' is guaranteed to be true.
|
||||
#
|
||||
# resource_available = false
|
||||
# puts "a1 consumed the resource"
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# a2 = Thread.new {
|
||||
# # Thread 'a2' behaves like 'a1'.
|
||||
# monitor.synchronize {
|
||||
# condvar.wait_until { resource_available }
|
||||
# resource_available = false
|
||||
# puts "a2 consumed the resource"
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# b = Thread.new {
|
||||
# # Thread 'b' periodically makes the resource available.
|
||||
# loop {
|
||||
# monitor.synchronize {
|
||||
# resource_available = true
|
||||
#
|
||||
# # Notify one waiting thread if any. It is possible that neither
|
||||
# # 'a1' nor 'a2 is waiting on 'condvar' at this moment. That's OK.
|
||||
# condvar.signal
|
||||
# }
|
||||
# sleep 1
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Eventually both 'a1' and 'a2' will have their resources, albeit in an
|
||||
# # unspecified order.
|
||||
# [a1, a2].each {|th| th.join}
|
||||
class ConditionVariable
|
||||
def initialize(monitor) # :nodoc:
|
||||
@monitor = monitor
|
||||
@cond = Thread::ConditionVariable.new
|
||||
end
|
||||
|
||||
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
|
||||
#
|
||||
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
|
||||
# even if no other thread doesn't signal.
|
||||
#
|
||||
def wait(timeout = nil)
|
||||
@monitor.mon_check_owner
|
||||
@monitor.wait_for_cond(@cond, timeout)
|
||||
end
|
||||
|
||||
#
|
||||
# Calls wait repeatedly while the given block yields a truthy value.
|
||||
#
|
||||
def wait_while
|
||||
while yield
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Calls wait repeatedly until the given block yields a truthy value.
|
||||
#
|
||||
def wait_until
|
||||
until yield
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Wakes up the first thread in line waiting for this lock.
|
||||
#
|
||||
def signal
|
||||
@monitor.mon_check_owner
|
||||
@cond.signal
|
||||
end
|
||||
|
||||
#
|
||||
# Wakes up all threads waiting for this lock.
|
||||
#
|
||||
def broadcast
|
||||
@monitor.mon_check_owner
|
||||
@cond.broadcast
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Mutex = Thread::Mutex
|
||||
ConditionVariable = Thread::ConditionVariable
|
||||
Queue = Thread::Queue
|
||||
SizedQueue = Thread::SizedQueue
|
||||
Mutex = Thread::Mutex # :nodoc:
|
||||
Monitor = Thread::Monitor # :nodoc:
|
||||
ConditionVariable = Thread::ConditionVariable # :nodoc:
|
||||
Queue = Thread::Queue # :nodoc:
|
||||
SizedQueue = Thread::SizedQueue # :nodoc:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user