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.
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
<< 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
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
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
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
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
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
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
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
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,
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