# Threads, the GVL, Fibers and Ractors — Ruby

Source: https://www.geekswithgeeks.com/en/ruby/p-concurrency

> Choose the right concurrency tool in CRuby.

## Concurrency in CRuby

CRuby has a **Global VM Lock (GVL)**: only one thread executes Ruby code at a time within a process. **Threads** still help with **I/O-bound** work, because the GVL is released while a thread waits on the network, disk or database, so several HTTP calls can run concurrently. They do not speed up **CPU-bound** Ruby code. For CPU parallelism, use **multiple processes** (as web servers such as **Puma** in clustered mode do, with several worker processes each running threads) or **Ractors**, an experimental actor-like model (Ruby 3.0+) where each Ractor runs in parallel with isolated objects and communicates by message passing; many gems are not yet Ractor-safe. **Fibers** are lightweight cooperative coroutines; with the **Fiber scheduler** interface (Ruby 3.0+) and libraries such as the **async** gem, thousands of concurrent I/O operations can run efficiently on one thread. Protect shared mutable state between threads with a **`Mutex`** (or use thread-safe structures such as `Queue`), and remember that background job systems such as **Sidekiq** handle much real-world concurrency for Rails apps.

## Concurrent HTTP calls with threads and a thread-safe queue

Threads help while waiting on I/O; a Mutex protects shared state.

```ruby
require "net/http"
require "json"

urls = %w[
  https://api.example.com/products/1
  https://api.example.com/products/2
  https://api.example.com/products/3
].map { URI(_1) }

results = {}
lock = Mutex.new

threads = urls.map do |uri|
  Thread.new do
    body = Net::HTTP.get(uri)                 # GVL released while waiting on the network
    lock.synchronize { results[uri.path] = JSON.parse(body) }
  rescue StandardError => e
    lock.synchronize { results[uri.path] = { error: e.message } }
  end
end
threads.each(&:join)
puts results.keys.inspect

jobs = Queue.new                               # thread-safe work queue
%w[pen ink notebook].each { jobs << _1 }
jobs.close
workers = 2.times.map { Thread.new { while (sku = jobs.pop) do puts "processing #{sku}" end } }
workers.each(&:join)
```

## One microphone at a meeting

The GVL is a single microphone: only one person (thread) can speak Ruby at a time. But while someone waits for a phone call (I/O), they pass the microphone on, so a meeting full of people waiting on calls still moves quickly.

**Quiz:** In CRuby, when do threads improve performance?

- [x] For I/O-bound work such as network or database calls, because the GVL is released while waiting
- [ ] For CPU-heavy calculations in pure Ruby
- [ ] Never
- [ ] Only on Windows

*Answer:* For I/O-bound work such as network or database calls, because the GVL is released while waiting. The GVL limits Ruby code to one thread at a time, but waiting on I/O releases it.
