#!/usr/bin/env ruby # STAQ threading & forking training 4/26/16 # # Invoke like so: # # ruby forking_and_threading.rb # ruby forking_and_threading.rb thread # ruby forking_and_threading.rb naive # seems to work OK # env RBENV_VERSION=jruby-9.0.4.0 ruby ./forking_and_threading.rb naive # why does this give different results? # # Can monitor the processes being created with this command: # watch -n 0.2 'ps -o command,pid,ppid,rss | grep ruby | grep -v grep' flag = ARGV[0] case flag when nil puts "Watch processes with this command:\nwatch -n 0.2 'ps -o pid,ppid,command | grep ruby | grep -v grep'" puts "*" * 30 puts "I'm the parent process, and my process ID is #{Process.pid}" pid = Process.fork { puts "\tI'm a child process, and my process ID is #{Process.pid}" sleep 20 } puts "I'm the parent process, and my process ID is #{Process.pid}. I'm aware of the fact that I have a child process #{pid}" Process.wait # Registers interest in the subprocess' state, so as to avoid a zombie process; will block when "spawn" pid = spawn({ "STAQ_VAL" => "100" }, RbConfig.ruby,%q|-e puts "\tHello from spawned process #{Process.pid} with STAQ_VAL of #{ENV.fetch("STAQ_VAL")}"; sleep 30|) puts "I'm parent process #{Process.pid} and I just spawned process #{pid}. Now I am detaching." Process.detach(pid) # Creates a thread responsible for reaping the identified process; see http://ruby-doc.org/core-2.2.0/Process.html#method-c-detach when "thread" require "thread" queue = Queue.new # this is a thread-safe object to share between threads threads = 5.times.map do |time| Thread.new do val = queue.pop sleep rand(1..3) puts "I'm in process #{Process.pid} and my thread number is #{time}. My thread ID is #{Thread.current.object_id}. I just received queue item #{val}" end end (100..500).step(100).each do |work| queue.push(work) end threads.each(&:join) # wait for all threads to finish when "naive" # This is not a great example, because MRI will actually produce consistent results, because we're relying on the GIL. # But when you run this in an interpreter without GIL (like JRuby or Rubinius) you can see the problem. It's hard to # create a simple scenario in MRI to clearly show the issue. Usually threading problems in MRI are more pernicious for # this reason! $bank_account_a = 0 $bank_account_b = 0 threads = 2000.times.map do Thread.new do # NEVER DO THIS $bank_account_a += 1 $bank_account_b += 1 end end threads.each(&:join) # Will get inconsistent answers for these puts $bank_account_a puts $bank_account_b when "mutex" # Will produce consistent results under any interpreter require "thread" mutex = Mutex.new $bank_account_a = 0 $bank_account_b = 0 threads = 2000.times.map do Thread.new do mutex.synchronize do $bank_account_a += 1 $bank_account_b += 1 end end end threads.each(&:join) # Will get inconsistent answers for these puts $bank_account_a puts $bank_account_b end