Sobes.tech
Middle+

How is preemptive multitasking implemented in Ruby?

sobes.tech AI

Answer from AI

In "pure" Ruby (MRI, JRuby, TruffleRuby runtimes), there is no true preemptive multitasking for parallel code execution within a single process due to the Global Interpreter Lock (GIL). The GIL allows only one thread to execute Ruby code at any given time.

However, preemptive multitasking exists at the input-output level in Ruby. When a thread performs blocking I/O (e.g., reading a file, making a network request), the GIL is released, allowing another thread to execute Ruby code. This enables concurrency for I/O-bound operations but not for CPU-bound tasks.

There are alternative Ruby implementations (e.g., Rubinius) that attempted to implement true multitasking without a GIL, but they did not gain as widespread adoption as MRI.

For parallel execution of CPU-bound tasks in Ruby, the common approaches are:

  • Processes (fork): Each process has its own GIL, allowing Ruby code to run in parallel. Communication between processes requires explicit mechanisms (IPC).
  • External libraries and C extensions: Operations implemented in C can run outside GIL control, providing parallelism.

In Ruby 3.0, a Fibers Scheduler was introduced, enabling asynchronous I/O operations using fibers and non-blocking calls. This improves performance for I/O-bound tasks but does not make CPU-bound tasks truly parallel within a single process.

# Example of using threads in MRI for I/O-bound tasks
require 'net/http'
require 'uri'

urls = %w[http://example.com http://ruby-lang.org]
threads = urls.map do |url|
  Thread.new do
    uri = URI.parse(url)
    response = Net::HTTP.get_response(uri)
    puts "Downloaded #{url} (Status: #{response.code})"
  end
end

threads.each(&:join)

Thus, although Ruby provides tools for working with threads, true preemptive multitasking for CPU-bound tasks is mainly achieved through multiple processes or specific Ruby implementations without a GIL.