Threads, Mutex & Ractor

A thread is a separate flow of execution that lets your program do several things at once, while a Mutex guards shared data and a Ractor unlocks true multi-core parallelism.

Learn Threads, Mutex & Ractor in our free Ruby course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll run work concurrently, protect shared state safely, and meet Ruby's path to real parallelism.

What You'll Learn in This Lesson

1️⃣ Threads, join, and the GVL

Thread.new {' '} launches a block concurrently; join makes the main thread wait for it to finish. Because thread finish order is unpredictable, we sort the collected results so the output is deterministic. Remember the GVL: threads interleave but only one runs Ruby code at a time.

2️⃣ Mutex: Safe Shared State

When several threads update the same variable, counter += 1 is three steps — read, add, write — and threads can interleave and lose updates. A Mutex fixes this: mutex.synchronize {' '} lets only one thread inside at a time, so the count is always correct.

3️⃣ Queue & a Glimpse of Ractor

A Queue is a thread-safe pipe — producers push , consumers pop , and locking is handled for you, ideal for worker pools. And when you truly need multiple cores, a Ractor runs in parallel with isolated memory, passing data in rather than sharing it.

And the parallel option — a Ractor sidesteps the GVL. Each one gets its own memory; you pass values in and take results out.

🎯 Your Turn

The threads compute partial sums, but the main thread races ahead before they finish. Replace the ___ with the method that waits for each thread.

Count words across several lines using one thread per line, a shared hash, and a Mutex to keep the tallies correct. Print sorted by word. Run with ruby wordcount.rb .

📋 Quick Reference — Concurrency

Practice quiz

What does Thread.new do?

  • Pauses the program
  • Creates a new process
  • Starts a block of code running concurrently
  • Locks a mutex

Answer: Starts a block of code running concurrently. Thread.new launches its block to run concurrently with the rest of the program.

What does calling join on a thread do?

  • Makes the caller wait until that thread finishes
  • Cancels the thread
  • Starts the thread
  • Merges two threads

Answer: Makes the caller wait until that thread finishes. join blocks the calling thread until the joined thread has finished.

Because of MRI Ruby's GVL (GIL), how many threads run Ruby code at the same instant?

  • As many as cores
  • Two
  • Unlimited
  • Exactly one

Answer: Exactly one. The Global VM Lock allows only one thread to execute Ruby code at any instant.

For which kind of work do Ruby threads provide real speedup despite the GVL?

  • CPU-bound number crunching
  • I/O-bound work like network and file reads
  • Nothing — threads never help
  • Only sorting

Answer: I/O-bound work like network and file reads. The lock is released during blocking I/O, so threads overlap well for I/O-bound work.

Why is an unguarded counter += 1 across threads unsafe?

  • It is read-modify-write, so threads can interleave and lose updates
  • It is a comment
  • It freezes the counter
  • It always raises an error

Answer: It is read-modify-write, so threads can interleave and lose updates. counter += 1 is read-modify-write; without protection threads interleave and updates are lost.

What does mutex.synchronize { ... } guarantee?

  • The block runs faster
  • The block never runs
  • Only one thread runs the protected block at a time
  • All threads run it together

Answer: Only one thread runs the protected block at a time. synchronize makes the protected block atomic — only one thread inside at a time.

What is a Queue in Ruby's concurrency toolkit?

  • A sorted array
  • A thread-safe pipe where producers push and consumers pop
  • A type of mutex
  • A database

Answer: A thread-safe pipe where producers push and consumers pop. A Queue is already thread-safe: push with << and pull with pop, locking handled internally.

Calling pop on a Queue that is empty (but not closed) will...

  • Raise an error
  • Return nil immediately
  • Close the queue
  • Block and wait until an item arrives

Answer: Block and wait until an item arrives. pop blocks while the queue is empty, which suits producer/consumer pipelines.

What makes a Ractor able to run in true parallel across cores?

  • It ignores the GVL by sharing memory
  • Each Ractor has isolated memory and sidesteps the GVL
  • It uses more threads
  • It disables the garbage collector

Answer: Each Ractor has isolated memory and sidesteps the GVL. Each Ractor has isolated memory and its own lock, so CPU work can use multiple cores.

How do you keep concurrent output deterministic when finish order is unpredictable?

  • Use more threads
  • Avoid join
  • Sort the collected results before printing
  • Print inside each thread

Answer: Sort the collected results before printing. Sorting the gathered results makes output stable regardless of thread finish order.