summaryrefslogtreecommitdiff
path: root/thread_sync.rb
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2025-12-13 06:35:58 +0100
committerJean Boussier <jean.boussier@gmail.com>2026-02-12 20:35:18 +0100
commitd8b8a95af9d6aab1e8da2b6f1808bc3ffd406889 (patch)
treebda49e00ed6d1df7c8dadc6296673500260f65ac /thread_sync.rb
parentf33073a6cb8e6800309de815a1acce3322401bd1 (diff)
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 ```
Diffstat (limited to 'thread_sync.rb')
-rw-r--r--thread_sync.rb186
1 files changed, 182 insertions, 4 deletions
diff --git a/thread_sync.rb b/thread_sync.rb
index 398c0d02b7..c9d37772d7 100644
--- a/thread_sync.rb
+++ b/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: