DEV Community

Yuta Miyama
Yuta Miyama

Posted on

3

Line by line explanation of Ruby 3 Ractor - worker pool implementation

Ruby 3 has an exciting update about its concurrency support: Ractors.

First, please read https://github.com/ruby/ruby/blob/master/doc/ractor.md for "What it is" and "What it can do".

If you're trying to achieve some sort of concurrent programming and ❤️ Ruby language, you should definitely give it a try.

Worker pool's implementation in Ractor

Below's the copy of the code explained in Ractor's official documentation. It's slightly modified to provide more granular explanation on its behaviour.

I've also annotated the code heavily, so that you can read the intent of each line with me.

Let's decipher this line by line, so that you can properly assess what you can do with Ractor.

For the basic API of Ractor, please refer to https://ruby-doc.org/core-3.0.0/Ractor.html

Code with line-by-line explanation

# This example computes if the given numbers is a prime number or not, using 10 parallel workers. 

# to make `num.prime?` possible.
require 'prime'

# this ractor will keep listening to the sent messages, and yield them to whomever want to take the value.
pipe = Ractor.new do
  loop do
    Ractor.yield Ractor.receive
  end
end

# Let's check the prime numbers from 1 upto 1000
N = 1000
# we'll use 10 workers to do this work.
RN = 10
# worker ractors initialization.
# each worker ractor takes pipe as its sharable object (Ractor has some synchronization mechanism according to [this doc](https://github.com/ruby/ruby/blob/master/doc/ractor.md#shareable-objects)) 
# then, each worker Ractor reads the input through the pipe (multiplexing) to utilize 10 workers
workers = (1..RN).map.with_index do |i|
  Ractor.new pipe, i do |pipe, i|
    # Ractor#take is a blocking call, and waits till pipe yields something
    while n = pipe.take
      # Worker, then in turn, computes something (expensive :) and yields to whomever willing to listen to this ractor
      Ractor.yield ["worker ##{i}}", n, n.prime?]
    end
  end
end

# sending 1000 numbers to the pipe, worker ractors are ready to consume by now.
(1..N).each{|i|
  pipe << i
}

# main process (main process itself is a running Ractor) calls
# Ractor#select, which can listen to a list of ractors
# because it's called (1..N) times, it'll hit all of the computed values by the time pp prints them.
pp (1..N).map{
  _r, (wid, n, b) = Ractor.select(*workers)
  [wid, n, b]
}.sort_by{|(wid, n, b)| n}
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope this illustration together with https://github.com/ruby/ruby/blob/master/doc/ractor.md has given you a better understanding of what you can do with Ractor.

In my opinion, Ruby finally providing basic primitives for doing actor based concurrency is very exciting.

You won't have to focus too much on the performance gain, or even the current status of all the APIs (they explicitly state its an experimental feature, as of Ruby 3.0.1).

still, I'm confident that we can already start learning how to write distributed systems all within the favourite language of our choice :)

I hope you'll dig a bit deeper by yourself, as well! Happy coding 🙌

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more