Ruby Academy · Lesson

The GVL Explained

Why threads are limited.

Lesson 2 of 413 steps

The GVL Explained is a free Ruby Academy lesson on CoddyKit. This is lesson 2 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Ruby Academy learning path, and your progress syncs across the web and the CoddyKit app. The Ruby Academy course includes 4 lessons in total.

What Is the GVL?

The GVL (Global VM Lock, historically the GIL) is a lock inside CRuby, the standard Ruby interpreter.

  • It allows only one thread to execute Ruby code at a time.
  • It protects the interpreter's internals from concurrent corruption.
  • It means pure-Ruby CPU work does not truly run in parallel on multiple cores.

Understanding the GVL explains when threads help and when they do not.

Why It Exists

CRuby's internals (object allocation, garbage collection, C extensions) were not all written to be thread-safe. The GVL makes them safe by serializing Ruby execution.

Removing it cleanly is hard, so CRuby keeps it. JRuby and TruffleRuby do not have a GVL and can run threads on many cores.

CPU-Bound Work

For CPU-bound tasks (math, sorting, parsing) threads do not speed things up under CRuby. Only one thread runs Ruby code at any instant, so two CPU threads roughly equal one thread plus overhead.

def heavy
  sum = 0
  1_000_00.times { |i| sum += i }
  sum
end

puts heavy
puts "CPU work does not parallelize under the GVL"

I/O Releases the GVL

The key insight: blocking I/O releases the GVL. When a thread sleeps, reads a file, or waits on a socket, it lets go of the lock so another thread can run Ruby.

This is why threads are great for I/O-bound work even with the GVL.

Overlapping Waits

Here three threads each sleep. Because sleep releases the GVL, the waits overlap. The total time is about 0.2s, not 0.6s.

start = Time.now

threads = (1..3).map do
  Thread.new { sleep 0.2 }
end
threads.each(&:join)

elapsed = Time.now - start
puts "Elapsed roughly: " + elapsed.round(1).to_s

C Extensions Can Release It

Well-written C extensions can release the GVL during long native computations using rb_thread_call_without_gvl. This lets heavy native work run in parallel while Ruby threads continue.

So some libraries achieve real parallelism even on CRuby.

Threads Still Switch

Even for CPU work the GVL does not freeze other threads forever. CRuby preempts a thread periodically so each gets a turn. The work is concurrent (interleaved) but not parallel (simultaneous).

results = []
mutex = Mutex.new

threads = (1..3).map do |n|
  Thread.new do
    mutex.synchronize { results << n }
  end
end
threads.each(&:join)

puts "Collected: " + results.sort.join(", ")

Concurrency vs Parallelism

Two important words:

  • Concurrency: tasks make progress by interleaving. The GVL allows this.
  • Parallelism: tasks run at the exact same instant on different cores. The GVL prevents this for Ruby code.

Threads give you concurrency; Ractors give you parallelism.

Getting Around the GVL

To use multiple cores for CPU work in CRuby you can:

  • Run multiple processes (each has its own GVL).
  • Use Ractors for in-process parallelism.
  • Offload to C extensions that release the GVL.

For I/O work, plain threads are usually enough.

Processes Have No Shared GVL

Each OS process runs its own Ruby VM with its own GVL. Forking creates true parallelism but processes do not share memory and use more resources.

puts "Main pid: " + Process.pid.to_s
puts "Each forked process has its own independent GVL"

Choosing a Strategy

Decision guide:

  • Lots of waiting? Use threads.
  • Heavy CPU, want cores? Use processes or Ractors.
  • Need shared state? Threads with a Mutex, but mind the GVL limits.

Knowing the GVL prevents the classic mistake of expecting CPU threads to scale linearly.

Quick Check

Test your understanding of the GVL.

Recap: The GVL

Key takeaways about the Global VM Lock:

  • Only one thread runs Ruby code at a time in CRuby.
  • It protects the interpreter's internals.
  • Blocking I/O releases it, enabling overlapping waits.
  • CPU-bound Ruby work does not parallelize across cores.
  • Use processes or Ractors for true parallelism.

Next we look at Fibers for lightweight cooperative concurrency.

Free to start

Learn Ruby with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
29
Lessons
109

Frequently Asked Questions

Is the “The GVL Explained” lesson free?

Yes — any 3 lessons of the Ruby Academy learning path, including “The GVL Explained”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Ruby Academy course includes 4 lessons in total.

What will I learn in “The GVL Explained”?

Why threads are limited. You practise Ruby Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Ruby Academy?

No prior experience is required. Ruby Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

How long does the “The GVL Explained” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Ruby Academy lesson?

Yes. Every Ruby Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Threads
  2. The GVL Explained
  3. Fibers
  4. Ractors
← Back to Ruby Academy