Introduced in Ruby 3.0, Ractors enable true Parallelism within a single Ruby interpreter by implementing the Actor Pattern for safe multithreaded behavior. Originally called “Guilds” during the experimental phase, Ractors solve Ruby’s long-standing challenge of CPU-bound parallel execution.
How Ractors Achieve Parallelism
Isolation Enables Parallelism
Ractors trade Ruby’s traditional shared-everything model for per-actor GVL isolation—sacrificing convenient shared state to unlock true multi-core execution without rewriting the interpreter.
Each Ractor maintains its own Global Interpreter Lock (GVL), allowing multiple Ractors to execute Ruby code simultaneously across different CPU cores. This fundamentally differs from traditional Ruby threads, which share a single GVL and thus cannot achieve true parallelism—see Thread Contention is GVL Queuing in Ruby for details on how the shared GVL limits thread execution.
By isolating the GVL per-Ractor, Ruby sidesteps the synchronization bottleneck that prevents parallel execution. Multiple Ractors can run Ruby code at the same time, each progressing independently on separate cores.
Isolation and Communication
Safety Through Prohibition
Ractors enforce thread safety by making shared mutable state impossible rather than synchronized—most objects cannot be shared at all, only copied, moved, or marked immutable.
Ractors achieve Thread Safety through strict isolation rather than locks. They cannot share most objects directly. Instead, communication happens through three mechanisms:
Shareable Objects: Objects marked with the RUBY_FL_SHAREABLE
flag (such as immutable values, symbols, and specially frozen objects) can be safely referenced by multiple Ractors without copying.
Message Passing: Ractors communicate via ports using send/receive operations. Data is transferred between Ractors rather than accessed via shared memory, following the actor model’s principles.
Object Movement: Objects can be “moved” from one Ractor to another, transferring ownership completely. The original Ractor loses access, preventing shared mutable state.
Object Copying: When moving isn’t appropriate, deep copying creates independent instances for each Ractor, ensuring isolation at the cost of memory overhead.
Ractors restrict access to variables defined outside their scope, preventing accidental sharing of mutable state that would violate isolation guarantees.
Internal Implementation
Ruby’s source code reveals sophisticated Ractor internals:
Lifecycle States: Each Ractor progresses through states—created → running → blocking → terminated—tracked internally for scheduling and coordination.
Native Mutex: Every Ractor has its own native mutex lock for internal synchronization, distinct from the per-Ractor GVL.
Ractor-Local Storage: Similar to thread-local storage but scoped to Ractors, allowing per-Ractor state without global sharing.
Garbage Collection: Ractors integrate with Ruby’s Incremental Garbage Collection in Ruby system, requiring coordination during GC phases to maintain memory safety across isolated heaps.
Error Classes: Special exceptions (Ractor::IsolationError
, Ractor::MovedError
, Ractor::ClosedError
) handle isolation violations and communication failures.
When to Use
Ractors excel at CPU-bound parallel workloads where you need true parallel execution across multiple cores. They’re appropriate when:
- Processing large datasets that can be partitioned independently
- Running computational tasks that benefit from multi-core hardware
- Building applications structured around the actor pattern
- Requiring process-like isolation with lower overhead than forking
For I/O-bound work, Async Ruby fibers or regular threads typically offer simpler solutions with better performance characteristics.
Caveats
The GVL Returns Inside Ractors
Threads within a single Ractor still share that Ractor’s GVL—parallelism exists only between Ractors, not within them, reintroducing the classic threading bottleneck at the actor level.
Ractors introduce complexity. If a Ractor runs multiple threads internally, those threads still share a single GVL within that Ractor—parallelism only exists between Ractors, not within them. They’re more complex than Async Ruby or regular threads, requiring careful design around message passing.
Not all Ruby objects are shareable by default. Most mutable objects must be copied or moved, potentially creating performance overhead. The isolation model requires rethinking traditional Ruby patterns that rely on shared state and global variables.
Despite these constraints, Ractors represent Ruby’s most significant advancement in Concurrency primitives, finally enabling true parallel execution of Ruby code.