Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
206: Parallel Processing with the parallel Gem
Whenever I'm dealing with I/O-bound tasks—like hitting an API a hundred times or resizing a folder full of images—the standard Ruby .each loop starts to feel like a bottleneck. We're spending most of our time waiting for the network or the disk, while our CPU just sits there idling. That's where the parallel gem comes in. It's a wrapper that makes it trivial to swap out a sequential loop for one that leverages multiple cores or threads.
The slow way to fetch profiles
Let's imagine we're building a tool to sync user data from a legacy API. I've got a list of user IDs, and I need to fetch a profile for each one. Since this is a "legacy" API, it's slow—maybe it takes a full second per request.
# First, gem install parallel
require 'parallel'
user_ids = (1..10).to_a
# Simulating a slow API call
def fetch_profile(id)
sleep(1)
{ id: id, name: "User #{id}", status: "Active" }
end
start_time = Time.now
user_ids.each do |id|
puts "Fetching #{id}..."
fetch_profile(id)
end
puts "Finished in #{Time.now - start_time} seconds"
# This takes exactly 10 seconds. Painful.
Flipping the switch to parallel
The beauty of the parallel gem is that the API mirrors Ruby's Enumerable methods. I can basically just replace .each with Parallel.each. By default, this gem uses processes (forking), which is great because it bypasses Ruby's Global Interpreter Lock (GIL), allowing us to actually use multiple CPU cores.
start_time = Time.now
Parallel.each(user_ids, in_processes: 4) do |id|
puts "Fetching #{id} in process #{Process.pid}..."
fetch_profile(id)
end
puts "Finished in #{Time.now - start_time} seconds"
# Now this takes about 3 seconds. Much better.
The "where did my data go?" moment
Here is where I usually trip up when I first start using this gem. I remember trying to collect the results of these API calls into a local array, like this:
results = []
Parallel.each(user_ids, in_processes: 4) do |id|
results << fetch_profile(id)
end
puts "Total results: #{results.length}"
# Expected: 10. Actual: 0.
I stared at this for five minutes before remembering how forking works. When Parallel uses in_processes, it creates a copy of the entire Ruby process. Each child process has its own version of the results array. They push the data into their own local copy, and when the child process dies, that memory vanishes. The original results array in the parent process stays completely empty.
Using map for a clean result set
If you actually need the data back from your parallel loop, don't try to push to an external array. Instead, use Parallel.map. This tells the gem to collect the return value of each block and aggregate them back into a single array in the parent process once everything is finished.
start_time = Time.now
# map collects the return values from the child processes
results = Parallel.map(user_ids, in_processes: 4) do |id|
fetch_profile(id)
end
puts "Collected #{results.length} profiles."
puts "Finished in #{Time.now - start_time} seconds"
One last tip: if your task is purely I/O (like waiting for a website to respond) and doesn't require heavy CPU lifting, you can use in_threads: 4 instead of in_processes. Threads are "lighter" on system resources than processes, though you have to be more careful about thread-safety if you're mutating shared objects.
📋 Practical Task
Building a Parallel Website Status Checker
You need to create a script that checks the HTTP status of a list of websites. Instead of checking them one by one, you'll use the parallel gem to speed up the process.
- Create an array of at least 5 URLs (e.g.,
'https://www.google.com','https://www.github.com', etc.). - Use
net/httpandurito write a method that takes a URL and returns a string:"URL: [url] - Status: [code]". - Use
Parallel.mapwithin_threads: 5to process the URLs. - Print the final array of results to the console.
- Ensure you wrap the execution in a timer (using
Time.now) to prove that the parallel execution is faster than a standard.maploop.
There are no comments for now.