Since Ruby 1.9, each Ruby Thread gets executed on its own native operating system thread.

But, as a performance optimization, Ruby will try to reuse native operating system threads if it can.

When a Ruby Thread terminates, Ruby will not immediately terminate the operating system thread that was hosting it. Instead, it will keep it around for a few seconds, and if in that time period the application starts a new Thread, then Ruby will reuse an existing operating system thread.

This can be observed on Ruby 3.1+ by using the new Thread#native_thread_id method:

puts RUBY_DESCRIPTION

def thread_info
  "Thread #{Thread.current} is being hosted by #{Thread.current.native_thread_id}"
end

puts Thread.new { thread_info }.value
puts Thread.new { thread_info }.value
puts Thread.new { thread_info }.value

puts "Sleeping for a bit..."
sleep 10

puts Thread.new { thread_info }.value

…​and here’s the result of running this example:

ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
Thread #<Thread:0x00007f0e5e985030 example1.rb:7 run> is being hosted by 44061
Thread #<Thread:0x00007f0e5e9841a8 example1.rb:8 run> is being hosted by 44061
Thread #<Thread:0x00007f0e5e98bcc8 example1.rb:9 run> is being hosted by 44061
Sleeping for a bit...
Thread #<Thread:0x00007f0e5e98b070 example1.rb:14 run> is being hosted by 44084

On older Rubies, Thread#native_thread_id did not not exist yet, but we can implement our own:

puts RUBY_DESCRIPTION

# Only for Linux; supporting other OS's is left as an exercise for the reader ;)
def current_native_thread_id
  File.readlink("/proc/thread-self").split("/").last
end

def thread_info
  "Thread #{Thread.current} is being hosted by #{current_native_thread_id}"
end

puts Thread.new { thread_info }.value
puts Thread.new { thread_info }.value
puts Thread.new { thread_info }.value

puts "Sleeping for a bit..."
sleep 10

puts Thread.new { thread_info }.value

…​and here’s the result of running this example on the older Ruby 2.7 series:

ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
Thread #<Thread:0x0000564a32b02110 example2.rb:12 run> is being hosted by 45998
Thread #<Thread:0x0000564a32afbdb0 example2.rb:13 run> is being hosted by 45998
Thread #<Thread:0x0000564a32af9f38 example2.rb:14 run> is being hosted by 45998
Sleeping for a bit...
Thread #<Thread:0x0000564a32af7e90 example2.rb:19 run> is being hosted by 46002

I hope you found this Ruby tidbit interesting!