Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby Concurrent

Ruby Concurrent

Concurrent programming in Ruby is hard. Before we start implement things with 'Thread.new', we should know the difference between Thread and Process, how to write thread safe code and what framework to use.

Jimmy Chao

July 27, 2016
Tweet

More Decks by Jimmy Chao

Other Decks in Programming

Transcript

  1. What is concurrent programming? • several computations are executed during

    overlapping time periods concurrently instead of sequentially
  2. We are always in Thread • One process has at

    least one thread • The smallest sequence of programmed instructions that can be managed independently by a scheduler • Thread.current • Process.pid
  3. Process vs Thread Process Thread Forked process are given a

    new virtual memory space Threads share the same memory space If parent process died before children have exited, children can become zombie process All thread die when the process dies Context switching is expensive and slow Context switching is fast and cheap
  4. Process.fork def send_emails 100.times do |i| fork do Mailer.deliver do

    from ‘jimmy#{i}@casecommons.org’ to ‘test#{i}@example.com’ subject ‘Threading and forking’ end end end Process.waitall end
  5. Thread.new def send_emails threads = [] 100.times do |i| threads

    << Thread.new do Mailer.deliver do from ‘jimmy#{i}@casecommons.org’ to ‘test#{i}@example.com’ subject ‘Threading and forking’ end end end threads.each(&:join) end
  6. GIL - Global Interpreter Lock • Used to prevent race

    condition from multi-thread in MRI • Implemented in C, protect C level ruby code to be thread safe • Only allow one thread to be run at a time • Not exists in JRuby and Rubinius
  7. Thread Safety def send_emails threads = [] sent_count = 0

    100.times do |i| threads << Thread.new do Mailer.deliver do from ‘jimmy#{i}@casecommons.org’ to ‘test#{i}@example.com’ subject ‘Threading and forking’ end sent_count += 1 end end threads.each(&:join) puts “We sent #{sent_count} emails” end
  8. Thread Safety def send_emails threads = [] sent_count = 0

    100.times do |i| threads << Thread.new do count = sent_count + 1 Mailer.deliver do from ‘jimmy#{i}@casecommons.org’ to ‘test#{i}@example.com’ subject ‘Threading and forking’ end send_count = count end end threads.each(&:join) puts “We sent #{sent_count} emails” end
  9. Thread Safety def collect_numbers threads = [] numbers = []

    100.times do |i| threads << Thread.new do 1000.times do |n| numbers << i * n end end end threads.each(&:join) puts “We have #{numbers.length} numbers” end
  10. Thread Safety def print_numbers threads = [] numbers = Queue.new

    100_000.times { |n| queue.push(n) } 100.times do |i| threads << Thread.new do if !queue.empty? puts queue.pop end end end threads.each(&:join) end
  11. Mutex • Lock to obtain before access shared memory •

    mutex.synchronize mutex = Mutex.new mutex.synchronize do count += 1 end
  12. Mutex class BlockingQueue def initialize @queue = [] @mutex =

    Mutex.new end def push(n) @mutex.synchronize do @queue << n end end def pop @mutex.synchronize do @queue.pop end end end
  13. Deadlock • multiple mutex lock each others, or one does

    not release lock mutexA = Mutex.new mutexB = Mutex.new status = ‘running’ Thread.new do mutexA.synchronize do mutexB.synchronize { status } end end Thread.new do mutexB.synchronize do mutexA.synchronize { status } end end
  14. Condition Variable • A api to signal thread to wait

    and resume mutex = Mutex.new condvar = ConditionVariable.new numbers = [] mutex.synchronize do while numbers.empty? condvar.wait mutex end process… end mutex.synchronize do numbers += 1000.times.map { |n| n } condvar.signal end
  15. The safest path to concurrency 1. Don’t do it 2.

    If you must do it, don’t share data across threads 3. If you must share data across threads, don’t share mutable data 4. If you must share mutable data threads, synchronize access to that data
  16. Concurrent frameworks • Abstract layer to handle concurrency in your

    code • Thin - event machine • ActionCable - event machine, nio • Sidekiq - concurrent ruby • celluloid • Puma • Ruby concurrent
  17. Sidekiq • Thread based background processor • Using concurrent-ruby -

    Map and Atomic::Fixnum • Processor run in independent Thread, and use redis for communication.
  18. Celluloid - Actor model • Actor can receive and response

    to message • Actor should not modified global variable
  19. Celluloid - Actor model class Counter include Celluloid attr_reader :count

    def initialize @count = 0 end def increment(n = 1) # time consuming calculation @count += n end end actor = Counter.new actor.increment # => 1 actor.async.increment(41) actor.count # block until increment finish, => 42,
  20. Concurrent Ruby • Modern concurrency tools including agents, futures, promises,

    thread pools, supervisors, and more. • Thread safe data structures • inspired by Erlang, Go, Clojure, Scala, Javascript
  21. Concurrent Ruby - Channel Channel = Concurrent::Channel def sum(a, c)

    sum = a.reduce(0, &:+) c << sum end a = [7, 2, 8, -9, 4, 0] l = a.length / 2 c = Channel.new Channel.go { sum(a[-l, l], c) } Channel.go { sum(a[0, l], c) } x, y = c.take, c.take puts [x, y, x+y].join(' ‘) # -5 17 12