Concurrency
&
Ruby
Rocky Jaiswal
RubyConf India 2013
WhY Concurrency?
About Me
Learning programming for the last 11 years
Did Java for around 8 years
Started learning Ruby ~3 years back
♥ Ruby
♥ the Ruby community
Also learning some CoffeeScript and Scala
http://rockyj.in
@whatsuprocky
Concurrency?
Concurrency is when two tasks can start, run, and complete in overlapping time periods
Concurrency can be implemented even in single processing units to speed things up
Concurrency is non-deterministic
Whereas a parallel program is one that merely runs on multiple processors, with the goal of hopefully running faster than it would on a single CPU
THREADS vs Processess
Threads are light weight processes that run in the same memory context
Ruby has Green Threads which are managed by the Ruby process
JRuby has real OS thread that run parallel to the parent thread
Sample Unicorn Setup
15 Unicorns = 15 Processes
1 Unicorn Process ~= 150 MB
15 Processes ~= 2 GB RAM*
Scaling this means more processes = more memory = more money
Also, If you are CPU bound you want to use no more unicorn processes than you have cores, otherwise you overload the system and slow down the scheduler.
Concurrency is good
JRuby + Puma / Torquebox
High-Scalability with less memory
Resque / Sidekiq
More workers and faster processing with less memory
SO is it all doom and gloom?
No!
- Most Rails applications are IO bound
- With MRI you are always thread safe
- MRI is getting faster and GC is getting better
-
Processes management is optimized
- Passenger is using a hybrid - evented + threaded / process architecture
Thread-Safety
Let me give you a demo
Appending to Arrays:
MRI Version
vs
JRuby Version
DEMO
RUN CODE on MRI & JRuby
array = []
5.times.map do
Thread.new do #Init 5 threads
1000.times do
array << nil #In each thread add 1000 elements to the Array
end
end
end.each(&:join)
puts array.size
Even APPENDING TO ARRAYS IS NOT THREAD SAFE!
What About Rails
config.threadsafe!
def threadsafe!
@preload_frameworks = true
@cache_classes = true
@dependency_loading = false
@allow_concurrency = true
self
end
BAD COUNTER CODE
class PagesController < ApplicationController
@counter = 0
class << self
attr_accessor :counter
end
#Classic read-modify-write problem
def index
counter = self.class.counter # read
sleep(0.1)
counter += 1 #update
sleep(0.1)
self.class.counter = counter # write
users = User.all
puts "-----------" + self.class.counter.to_s + "------------"
end
end
UGLY SYNCHRONIZED CODE
class PagesController < ApplicationController
@counter = 0
@semaphore = Mutex.new
class << self
attr_accessor :counter
attr_accessor :semaphore
end
def index
#counter = self.class.counter # read
sleep(0.1)
self.class.semaphore.synchronize {
self.class.counter += 1 #update
}
sleep(0.1)
#self.class.counter = counter # write
users = User.all
puts "-----------" + self.class.counter.to_s + "------------"
end
end
Rails 4 is concurrency enabled by default
concurrency introduces
Race Conditions
Deadlocks
Starvation
etc.
But gives you
Speed
Less Memory Usage
Safe concurrency
- Don't do it.
- If you must do it, don't share data across threads.
- If you must share data across threads, don't share mutable data.
-
If you must share mutable data across threads, synchronize access to that data.
Thread Safety in JRuby
Locks
ATOMICITY
IMMUTABILITY
ATOMIC Counter
java_import 'java.util.concurrent.atomic.AtomicInteger'
class PagesController < ApplicationController
@counter = AtomicInteger.new(1)
class << self
attr_accessor :counter
end
def index
sleep(0.1)
counter = self.class.counter.getAndIncrement() #update
sleep(0.1)
users = User.all
puts "-----------------" + counter.to_s + "-----------------"
end
end
All this sucks!
95% of syncronized code is broken. The other 5% is written by Brian Goetz. - Venkat Subramaniam
The Actor Model
Introduced by Carl Hewitt in 1973
Contributions by a lot of scholars and universities
Popularized by Erlang, now in Scala
- Simple and high-level abstractions for concurrency and parallelism
- Objects are Actors each with their own state which is never shared
- Communication happens through messages
-
Very lightweight event-driven processes (approximately 2.7 million actors per GB RAM [Akka])
THE ACTOR MODEL -2
Easier to deal with humans than with threads
Like humans, Actors communicate via messages
No state sharing, communicate via immutable messages
Implementations
Producer Consumer problem
Demo with JRuby + Locks
Demo with JRuby + Celluloid
Producer consumer
with locks
https://gist.github.com/rocky-jaiswal/5847810
Producer Consumer
with actors
HTTPS://GIST.GITHUB.COM/ROCKY-JAISWAL/5847814
Summary
-
Concurrency is the need of the hour
-
MRI is thread safe by default due to GIL / GVL
-
JRuby gives you real concurrency (RBX as well)
-
With power comes responsibility
-
Don't worry, concurrency can be easy if you follow the ground rules
-
If you want to write concurrent code yourself, use Actors
* I did not cover STM (provided by Clojure)
Thank you!
Questions
#A lot of this content has been taken from blogs, wikis and books. I do not claim it is my own and I wholeheartedly thank everyone who helped me with this presentation.