Giới thiệu về gem Parallel

Parallel là một gem hỗ trợ xử lý song song của ruby. Hiện nay Ruby có 2 thư viện cơ bản Thread và Process. Tuy nhiên có rất nhiều vấn đề khi sử dụng 2 thư viện này cần phải giải quyết . Parallel là một vài thư viện có cách sử dụng khá đơn giản đặc biệt với các tác vụ như đọc và ghi dữ liệu.

Sơ lược về xử lý song song

  • So với lập trình tuần tự, lập trình song song có thể hiểu một cách đơn giản là: lập trình song song chia nhỏ vấn đề đó ra và đưa vào xử lý thành nhiều phần
  • Lập trình song song gồm có Multi Threading (đa luồng) và Multiple Processes(đa xử tiến trình)
  • Multi Threading: có thể xem như là một chương trình con trong chương trình chính, giao tiếp với nhau thông qua biến toàn cục và theo kiến trúc chia sẻ bộ nhớ. Nếu như chỉ có 1 CPU thì ta vẫn có thể thực hiện kỹ thuật này nhưng không thể xử lý đa nhân được. Tuy nhiên xử lý đa luồng vẫn mang nhiều nét của lập trình tuần tự.
  • Multiple Processes: Thường là xử lý trên một tập dữ liệu và đặc tính của nó là xử lý song song các dữ liệu trên mỗi CPU. Nhiều công việc cùng làm chung trên một dữ liệu, mỗi công việc có 1 phân vùng dữ liệu để làm việc riêng.
  • Trong rails, xử lý MultiThreading có thể kế đến Sidekiq, Puma, Thin, và Multiple Processes: Unicorn
  • Đối với dữ liệu nhỏ, xử lý song song không mang lại hiệu quả cao bằng xử lý tuần tự, tuy nhiên với những dữ liệu lớn, xử lý song song sẽ nhanh hơn rất nhiều.

Cơ bản về gem Parallel

  • Cài đặt: gem install parallel
  • Parallel có thể xử lý đa luồng và đa tiến trình

Một số func cơ bản cho Multi Theard và Multiple Processes

# 2 CPUs -> work in 2 processes (a,b + c)
results = Parallel.map(['a','b','c']) do |one_letter|
  expensive_calculation(one_letter)
end 
# 3 Processes -> finished after 1 run
results = Parallel.map(['a','b','c'], in_processes: 3) { |one_letter| ... }
# 3 Threads -> finished after 1 run
results = Parallel.map(['a','b','c'], in_threads: 3) { |one_letter| ... }

Tương tự có thể sử dụng với each

Parallel.each(['a','b','c']) { |one_letter| ... }

ActiveRecord

Các bạn có thể áp dụng parallel cho ActiveRecord

# reproducibly fixes things (spec/cases/map_with_ar.rb)
Parallel.each(User.all, in_processes: 8) do |user|
  user.update_attribute(:some_attribute, some_value)
end
User.connection.reconnect!
# maybe helps: explicitly use connection pool
Parallel.each(User.all, in_threads: 8) do |user|
  ActiveRecord::Base.connection_pool.with_connection do
    user.update_attribute(:some_attribute, some_value)
  end
end
# maybe helps: reconnect once inside every fork
Parallel.each(User.all, in_processes: 8) do |user|
  @reconnected ||= User.connection.reconnect! || true
  user.update_attribute(:some_attribute, some_value)
end

Break

Parallel.map(User.all) do |user|
  raise Parallel::Break # -> stops after all current items are finished
end

Kill

Chỉ nên sử dụng cách này nếu sub-process an toàn để kill process ở bất kỳ thời điểm nào

Parallel.map([1,2,3]) do |x|
  raise Parallel::Kill if x == 1# -> stop all sub-processes, killing them instantly
  sleep 100
end

Worker number

Sử dụng Parallel.worker_number để tìm ra vị trí của worker mà tác vụ đang được thực thi.

Parallel.each(1..5, :in_processes => 2) do |i|
  puts "Item: #{i}, Worker: #{Parallel.worker_number}"
end

# Item: 1, Worker: 1
# Item: 2, Worker: 0
# Item: 3, Worker: 1
# Item: 4, Worker: 0
# Item: 5, Worker: 1

Trên đây là giới thiệu sơ bộ về gem parallel. Các bạn có thể tham khảo thêm tại: https://github.com/grosser/parallel