Feature #21788
closedPromote Thread::Monitor to a core class
Description
Monitor is about as useful as Mutex and yet one is a core class and the other is a "stdlib" extension.
I propose to promote Thread::Monitor as a core class convenience.
The rest of monitor.rb
¶
The monitor stdlib also contains MonitorMixin, but I think this part can remain in lib/monitor.rb (or even be moved to lib/monitor_mixin.rb.
One more questionable part is Monitor#new_cond, as it returns a ::MonitorMixin::ConditionVariable so it would also need to remain in the stdlib.
Performance benefit¶
By being a core class, Monitor can also be implemented more efficiently, as it is no longer constrained to the public C API.
On my branch, monitor is approximately 15% faster (but there might be some more optimizations to be found).
master:
ruby 4.0.0dev (2025-12-17T07:16:32Z master 54d3945ee5) +YJIT +PRISM [arm64-darwin25]
Warming up --------------------------------------
Mutex 1.952M i/100ms
Monitor 1.775M i/100ms
Calculating -------------------------------------
Mutex 25.102M (± 0.4%) i/s (39.84 ns/i) - 126.884M in 5.054731s
Monitor 21.189M (± 0.1%) i/s (47.19 ns/i) - 106.485M in 5.025493s
core monitor:
ruby 4.0.0dev (2025-12-17T07:21:34Z core-monitor 32815a1bc4) +YJIT +PRISM [arm64-darwin25]
Warming up --------------------------------------
Mutex 1.975M i/100ms
Monitor 1.936M i/100ms
Calculating -------------------------------------
Mutex 25.213M (± 0.4%) i/s (39.66 ns/i) - 126.407M in 5.013728s
Monitor 24.099M (± 0.1%) i/s (41.50 ns/i) - 121.992M in 5.062092s
Proposed implementation: https://github.com/ruby/ruby/pull/15538
Updated by Eregon (Benoit Daloze) 2 months ago
· Edited
+1
I think MonitorMixin and MonitorMixin::ConditionVariable are small enough that it would be good to have them in core too.
BTW wait_while/wait_until are good patterns and maybe something Thread::ConditionVariable should have too, out-of-topic I know but the point is MonitorMixin seems well done and core-worthy.
mon_initialize could also be made safer potentially if it's made core, e.g. to use an internal lock on the object (or the GVL) or so.
Updated by byroot (Jean Boussier) 2 months ago
- Description updated (diff)
Updated by byroot (Jean Boussier) about 2 months ago
the point is MonitorMixin seems well done and core-worthy.
Yeah, I don't think I agree with this. I never use it because including it expose a bunch of mon_* public methods that nobody ever use.
At the end of the day 99% of Monitor usage is just:
class MyClass
def initialize
@monitor = Monitor.new
end
def some_method
@monitor.synchronize do
# do thing
end
end
end
I really see no value in the mixin.
Updated by shugo (Shugo Maeda) about 1 month ago
I am neutral on whether to make monitor built-in partially or fully, but backward compatibility for code that calls require 'monitor' should be maintained.
Speaking of MonitorMixin, it's just a shorthand form inspired by Java's synchronized mechanism, where the object representing the protected resource itself acts as a synchronization primitive.
However, MonitorMixin::ConditionVariable is even more critical, as it's essential for coordinating with other threads that need to access the protected resource.
In Java's synchronized mechanism, the protected object itself also serves as a condition variable. However, this necessitates using broadcast (notifyAll) instead of signal (notify) when waiting on multiple conditions. To address this, monitor decouples condition variables into separate objects.
Updated by ko1 (Koichi Sasada) about 1 month ago
Monitor is about as useful as Mutex and yet one is a core class and the other is a "stdlib" extension.
Do you mean it is useful because of recursive mutex? If it is the reason, Mutex.new(recursive: true) or something similar is another idea.
Updated by byroot (Jean Boussier) about 1 month ago
Do you mean it is useful because of recursive mutex?
It's very often used as one.
In my case I'm interested in speeding it up, to help speedup synchronized constructs such as connection pools, so I need the condition variable.
Updated by Eregon (Benoit Daloze) about 1 month ago
ko1 (Koichi Sasada) wrote in #note-5:
Do you mean it is useful because of recursive mutex? If it is the reason,
Mutex.new(recursive: true)or something similar is another idea.
Mutex is already very well established as "non-recursive/non-reentrant" and Monitor as recursive/reentrant so I think Mutex.new(recursive: true) would only add confusion now.
I think it's a good idea to make Monitor core, and I mostly agree with the PR at https://github.com/ruby/ruby/pull/15538.
Main remaining discussion point for me is https://github.com/ruby/ruby/pull/15538#discussion_r2659721652, i.e. I think all Monitor methods should be in core, and monitor.rb would only contain MonitorMixin.
MonitorMixin::ConditionVariable is moved to Monitor::ConditionVariable which is a good cleanup as that's usable without MonitorMixin.
Updated by Eregon (Benoit Daloze) about 1 month ago
BTW both Mutex and Monitor support ConditionVariable, so I don't think there is an argument that a Monitor is more than just a reentrant Mutex, it's not, it's just that.
Updated by ko1 (Koichi Sasada) 14 days ago
BTW, why performance is improved?
Updated by byroot (Jean Boussier) 14 days ago
BTW, why performance is improved?
For the most part is because being in core allow to remove many GET_EC() calls.
First being in core we can use the Primitive API, so we recieve the ec as first argument, then being inside thread_sync.c, we can directly pass down the ec to rb_mut_trylock and a few other routines.
GET_EC() is quite costly as it was made non-inlineable: https://github.com/ruby/ruby/blob/2967f98f305b95e7c910facd2d9b3381bd0296a8/vm_core.h#L2116-L2128
Updated by byroot (Jean Boussier) 11 days ago
- Status changed from Open to Closed
Applied in changeset git|d8b8a95af9d6aab1e8da2b6f1808bc3ffd406889.
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:
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