Skip to content

Instantly share code, notes, and snippets.

@subelsky
Last active June 17, 2020 14:34
Show Gist options
  • Save subelsky/c4beeb5ab69d9da454e738bb0dabcfd4 to your computer and use it in GitHub Desktop.
Save subelsky/c4beeb5ab69d9da454e738bb0dabcfd4 to your computer and use it in GitHub Desktop.
Ruby Threading & Forking Demo
#!/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