Home What Is the GIL in MRI Ruby?

What Is the GIL in MRI Ruby?

The GIL (Global Interpreter Lock) is a mutex inside MRI Ruby that ensures only one thread executes Ruby bytecode at a time. It does not mean Ruby is single threaded, and it does not block all parallel work. It is a design choice that simplifies memory management and keeps C extensions safe.

In MRI Ruby, GIL allows only one thread to execute Ruby bytecode at a time, even on multi-core CPUs.

What the GIL does

  • protects MRI internal data structures from concurrent mutation
  • allows Ruby threads to exist, but only one runs Ruby code at a time
  • releases the lock around many blocking I/O operations

This means CPU-bound Ruby code does not parallelize across cores, while I/O-bound work can still benefit from threads.

Why MRI keeps a GIL

MRI is a C-based interpreter with a large ecosystem of C extensions. The GIL:

  • makes object access safer without heavy locking everywhere
  • keeps extension authors from handling complex thread safety
  • simplifies garbage collection

Other Ruby implementations (JRuby, TruffleRuby) do not use a GIL, but they also have different runtimes and extension models.

Example: I/O-bound work still gains concurrency

In this example, the threads spend most time waiting on I/O, so the GIL is released and requests overlap.

require "net/http"
require "benchmark"

urls = [
  "https://example.com",
  "https://example.org",
  "https://example.net"
]

elapsed = Benchmark.realtime do
  threads = urls.map do |url|
    Thread.new do
      Net::HTTP.get(URI(url))
    end
  end
  threads.each(&:join)
end

puts "Elapsed: #{elapsed.round(2)}s"

You should see elapsed time closer to the slowest request, not the sum of all three. That is real concurrency, even with the GIL.

Example: CPU-bound code does not speed up

This example is CPU-heavy Ruby code, so the GIL prevents true parallelism.

def cpu_work(n)
  x = 0
  n.times { x += 1 }
  x
end

start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
threads = 4.times.map do
  Thread.new { cpu_work(30_000_000) }
end
threads.each(&:join)
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)

puts "Elapsed: #{(finish - start).round(2)}s"

Running this with more threads usually does not speed up on MRI. Use processes or a different Ruby runtime for CPU-bound parallelism.

Common myths about the GIL

  • Myth: “Ruby cannot do concurrency.” Reality: Ruby threads are real and great for I/O-bound work.
  • Myth: “The GIL makes threads useless.” Reality: Threads still help with waiting on network, disk, or DB.
  • Myth: “The GIL is only a bug.” Reality: It is a trade-off that keeps MRI simpler and safer.
  • Myth: “The GIL blocks all C extensions.” Reality: Well-written extensions release the GIL for long work.

Practical guidance for Rails

  • Use threads for I/O-heavy tasks (HTTP calls, DB reads).
  • Use processes for CPU-bound work (image processing, heavy JSON).
  • Background jobs can mix both: threads within a process for I/O, multiple worker processes for CPU.

Summary

The MRI GIL is a global lock that serializes Ruby bytecode execution. It limits CPU-bound parallelism but allows effective I/O concurrency. When you know the trade-offs, you can choose the right concurrency model for your Rails workload.

Share this post

Comments