# Basic Ractor usage
ractor = Ractor.new do
# This runs in parallel, isolated from main thread
result = heavy_computation
result
end
# Wait for result
result = ractor.take # Blocks until ractor finishes
# Passing data to ractors
ractor = Ractor.new(10) do |n|
n ** 2
end
ractor.take # => 100
# Communication via messages
ractor = Ractor.new do
loop do
message = Ractor.receive
puts "Received: #{message}"
break if message == :stop
end
end
ractor.send("Hello")
ractor.send("World")
ractor.send(:stop)
# Parallel processing with multiple ractors
def parallel_map(array)
ractors = array.map do |item|
Ractor.new(item) do |value|
# CPU-intensive operation
expensive_computation(value)
end
end
ractors.map(&:take)
end
results = parallel_map([1, 2, 3, 4, 5])
# Worker pool pattern
class RactorPool
def initialize(size)
@queue = Ractor.new do
jobs = []
loop do
Ractor.yield(jobs.shift) while jobs.any?
jobs << Ractor.receive
end
end
@workers = size.times.map do
Ractor.new(@queue) do |queue|
loop do
job = queue.take
result = job.call
Ractor.yield(result)
end
end
end
end
def submit(job)
@queue.send(job)
end
def results
@workers.map { |w| w.take }
end
end
pool = RactorPool.new(4)
10.times { |i| pool.submit(-> { i ** 2 }) }
pool.results
# Ractor limitations (by design for safety)
# Cannot share mutable objects
data = "hello"
Ractor.new(data) do |str|
str.upcase! # Error! Cannot modify shared object
end
# Must freeze or copy data
data = "hello".freeze
Ractor.new(data) do |str|
str.upcase # OK - frozen string
end
# Only shareable objects can be passed:
# - Immutable objects (numbers, symbols, true, false, nil)
# - Frozen objects
# - Ractor objects themselves
# - Classes/modules
# Async gem for structured concurrency
require 'async'
# Basic async task
Async do
puts "Hello"
sleep 1
puts "World"
end
# Concurrent HTTP requests
require 'async/http/internet'
Async do
internet = Async::HTTP::Internet.new
# Make requests concurrently
responses = Async do |task|
[
task.async { internet.get("https://api1.example.com/data") },
task.async { internet.get("https://api2.example.com/data") },
task.async { internet.get("https://api3.example.com/data") }
].map(&:wait)
end
responses.each do |response|
puts response.read
end
ensure
internet&.close
end
# Thread basics
threads = 5.times.map do |i|
Thread.new do
sleep rand
puts "Thread #{i} finished"
end
end
threads.each(&:join) # Wait for all threads
# Thread-safe counter with Mutex
class Counter
def initialize
@count = 0
@mutex = Mutex.new
end
def increment
@mutex.synchronize do
@count += 1
end
end
def value
@mutex.synchronize { @count }
end
end
counter = Counter.new
threads = 100.times.map do
Thread.new do
100.times { counter.increment }
end
end
threads.each(&:join)
counter.value # => 10000 (thread-safe!)
# Concurrent-ruby primitives
require 'concurrent'
# Atomic counter
counter = Concurrent::AtomicFixnum.new(0)
threads = 100.times.map do
Thread.new { 100.times { counter.increment } }
end
threads.each(&:join)
counter.value # => 10000
# Thread pool
pool = Concurrent::FixedThreadPool.new(5)
10.times do |i|
pool.post do
puts "Task #{i} on thread #{Thread.current.object_id}"
sleep 1
end
end
pool.shutdown
pool.wait_for_termination
# Future (async computation)
future = Concurrent::Future.execute do
# Long running computation
sleep 2
42
end
puts "Doing other work..."
result = future.value # Blocks until ready
puts "Future result: #{result}"
# Promise (manual fulfillment)
promise = Concurrent::Promise.new do |value|
value * 2
end
Thread.new do
sleep 1
promise.execute(21)
end
promise.value # => 42
# Actor pattern
class Counter
include Concurrent::Async
def initialize
@count = 0
super()
end
def increment
@count += 1
end
def value
@count
end
end
counter = Counter.new
100.times { counter.async.increment }
sleep 0.1
counter.value # Thread-safe!
# Concurrent map
results = Concurrent::Promise.zip(
*[1, 2, 3, 4, 5].map { |i|
Concurrent::Promise.execute { expensive_operation(i) }
}
).value
Ruby 3+ introduces Ractors for true parallelism without GIL limitations. Ractors are isolated actors—no shared mutable state. I use Ractors for CPU-intensive parallel processing. Messages pass between Ractors via send and receive. Async gem provides structured concurrency—fibers for non-blocking I/O. Concurrent-ruby gem offers thread-safe primitives—Atomic, ThreadPool, Future, Promise. Understanding Ruby's GIL limits thread parallelism helps choose right concurrency tool. Ractors excel for parallel computations; fibers for I/O concurrency. Thread safety requires careful synchronization. Mutex guards shared state. Testing concurrent code needs deterministic fixtures. Concurrency unlocks Ruby's full CPU potential.