Concurrency Vs Parallelism

Concepts Every Developer Should Know: Concurrency vs. Parallelism in Ruby

Parallelism and concurrency are two terms that often create confusion among developers. They are related but distinct concepts, each playing a crucial role in modern computing and software development.

As Rob Pike, one of the creators of Golang, succinctly put it: “Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.”

What is Concurrency?

Concurrency is about managing multiple tasks at once, intermixing them to optimize resource usage. In modern systems, concurrency is driven by design principles that ensure tasks or processes run efficiently, whether the hardware has one or multiple processors. Even with a single CPU, concurrency patterns allow tasks to share processor time effectively, creating an illusion of parallel execution.

These patterns enable parts of a program to be executed out of sequence or in partial order while still preserving the intended behavior of the program. This is crucial for applications that need to handle multiple tasks simultaneously, such as web servers that manage multiple client requests concurrently.

What is Parallelism?

Parallelism, on the other hand, involves executing multiple tasks simultaneously. It requires hardware support, such as multi-core or multi-processor systems, to allow different tasks to run at the same time. This distinction between concurrency (task management) and parallelism (task execution) significantly impacts application performance and efficiency.

Parallelism is particularly beneficial for compute-intensive applications, where tasks can be distributed across multiple processors to be executed simultaneously, leading to faster and more efficient processing.

Concurrency in Ruby

Ruby, as a programming language, offers several tools and libraries to handle concurrency. Here are some key concepts and examples:

Threads in Ruby: Ruby has built-in support for threads, which allow concurrent execution of code. However, due to Ruby’s Global Interpreter Lock (GIL), true parallel execution is restricted.

threads = []

5.times do |i|
  threads << Thread.new do
    puts "Thread #{i} is running"
  end
end

threads.each(&:join)

Fibers in Ruby: Fibers are a lower-level concurrency primitive in Ruby, providing cooperative multitasking. Unlike threads, fibers are not preemptive and must yield control explicitly.

fiber = Fiber.new do
  puts "Fiber running"
  Fiber.yield
  puts "Fiber resumed"
end

fiber.resume
puts "Main thread"
fiber.resume

EventMachine: EventMachine is a Ruby library for event-driven programming, enabling high-performance I/O operations.

require 'eventmachine'

EM.run do
  EM.add_timer(1) do
    puts "Timer executed after 1 second"
    EM.stop
  end
end

Celluloid: Celluloid is a concurrent object framework for Ruby, making it easier to build fault-tolerant, concurrent applications.

require 'celluloid/autostart'

class Worker
  include Celluloid

  def perform_task
    puts "Task performed"
  end
end

worker = Worker.new
worker.perform_task

Parallelism in Ruby

True parallelism in Ruby can be achieved using the parallel gem or the fork method to create separate processes. This bypasses the GIL and allows real parallel execution.

Parallel Gem: The parallel gem simplifies parallel processing of data.

require 'parallel'

results = Parallel.map([1, 2, 3, 4, 5]) do |number|
  number * 2
end

puts results

Forking Processes: Forking creates a new process, allowing parallel execution.

5.times do |i|
  fork do
    puts "Process #{i} is running"
  end
end

Process.waitall

Asynchronous Programming

Asynchronous programming is used to achieve concurrency in single-threaded environments. This approach enables a program to initiate tasks without waiting for previous ones to finish, managing multiple tasks in a non-blocking manner. A great example is Node.js, which handles concurrency in a single-threaded model using callbacks and event loops.

Meanwhile, multi-threaded environments (e.g., C#) facilitate both concurrency and parallelism, allowing concurrent task execution and true parallel execution across multiple processors or cores simultaneously.

Conclusion

Understanding the difference between concurrency and parallelism is crucial for building high-performing and efficient software solutions. Concurrency is about managing lots of tasks at once, optimizing resource usage, while parallelism is about executing multiple tasks simultaneously to improve performance.

By leveraging Ruby’s concurrency tools and understanding its parallelism capabilities, developers can build robust applications that handle multiple tasks efficiently. This distinction is key to optimizing application performance and ensuring that software solutions can scale effectively.