Last active
June 17, 2020 14:34
-
-
Save subelsky/c4beeb5ab69d9da454e738bb0dabcfd4 to your computer and use it in GitHub Desktop.
Ruby Threading & Forking Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Further reading: