comparison Theory of Concurrency Models What is concurrent-ruby? General purpose concurrency abstractions Thread-safe value objects, structures and collections Thread-safe variables Threadpools Thread Sychronization classes and algorithms Edge features of concurrent-ruby library
processing using multiple processors. The speedup limited by the time needed for the sequential fraction of the program If N is the number of processors, s is the time spent by a processor on serial part of a program, and p is the time spent by a processor on a parallel part of a program, then the maximum possible speedup is given by: 1 / (s+p/N) Synchronization & communication overhead
verifies that other threads have not concurrently made changes to memory that it accessed in the past. This final operation, in which the changes of a transaction are validated and, if validation is successful, made permanent, is called a commit…”
when we’re ready to commit” # Thread 1 atomic { - read a variable - increment a variable - write a variable } # Thread 2 atomic { - read variable - increment variable # going to write but Thread1 has written a variable… # notices Thread1 changed data, so ROLLS BACK - write variable }
as a tool for specifying and verifying the concurrent aspects of variety of different systems Processes - No threads. No shared memory. Fixed number of processes. Channels - Communication is synchronous (Unlike Actor model) Influences on design Go, Limbo
channels heavily, alternative to locks Cons Handling very big messages, or a lot of messages, unbounded buffers Messaging is essentially a copy of shared
may block (synchronous) Only receive blocks Messages are delivered when they are sent No guarantee of delivery of messages Synchronous Send message and forget Works on one machine Work on multiple machines (Distributed by default) Lacks fault tolerance Fault tolerance
Interpreter Lock) What happens with GVL? With GVL, only one thread executes a time Thread must request a lock If lock is available, it is acquired If not, the thread blocks and waits for the lock to become available Ruby’s runtime guarantees thread safety. But it makes no guarantees about your code.
GVL You can still write performant concurrent (as good as Java, Node.js) in a Ruby app if it does only heavy IO Multithreaded CPU-bound requests GVL is still issue. Ruby is fast enough for IO (network) heavy applications (In most cases)
to corrupt data) Avoids race conditions C extensions It makes C extensions development easier Most C libraries are not thread safe Parts of Ruby’s implementation aren’t thread safe (Hash for instance)
not have actor model Ruby didn’t have STM Ruby didn’t have better concurrency abstractions. Ruby has concurrent-ruby gem now concurrent-ruby gem provides concurrency aware abstractions (Inspired from other languages)
library for Ruby can ever prevent the user from making thread safety mistakes. All the library can do is provide safe abstractions which encourage safe practices. Thread Safety
other Ruby library Many of these abstractions support the mantra of "Do not communicate by sharing memory; instead, share memory by communicating". Thread Safety
end horn = Echo.new horn.echo('zero') # synchronous, not thread-safe # returns the actual return value of the method horn.async.echo('one') # asynchronous, non-blocking, thread-safe # returns an IVar in the :pending state horn.await.echo('two') # synchronous, blocking, thread-safe # returns an IVar in the :complete state
"(#{time}) Execution successfully returned #{result}\n" elsif ex.is_a?(Concurrent::TimeoutError) print "(#{time}) Execution timed out\n" else print "(#{time}) Execution failed with error #{ex}\n" end end end General Purpose Concurrency Abstraction
is a shared, mutable variable providing independent, uncoordinated, asynchronous change of individual values. Best used when the value will undergo frequent, complex updates. Suitable when the result of an update does not need to be known immediately Thread-safe variable
set + [set[-2..-1].reduce{|sum,x| sum + x }] end # create an agent with an initial value agent = Concurrent::Agent.new(next_fibonacci) # send a few update requests 5.times do agent.send{|set| next_fibonacci(set) } end # wait for them to complete agent.await # get the current value agent.value #=> [0, 1, 1, 2, 3, 5, 8] Thread-safe variable
state. At any time the value of the atom can be synchronously and safely changed Suitable when the result of an update must be known immediately. Thread-safe variable
set + [set[-2..-1].reduce{|sum,x| sum + x }] end # create an atom with an initial value atom = Concurrent::Atom.new(next_fibonacci) # send a few update requests 5.times do atom.swap{|set| next_fibonacci(set) } end # get the current value atom.value #=> [0, 1, 1, 2, 3, 5, 8] Thread-safe variable
the current thread is holding only a read lock, not a write # lock. So other threads can take read locks, but not a write lock. lock.release_read_lock # Now the current thread is not holding either a read or write lock, so # another thread could potentially acquire a write lock. Concurrent::ReentrantReadWriteLock Thread Synchronization Classes & Algorithms
where concurrent actors exchange messages. Channel: Communicating Sequential Processes (CSP). Functionally equivalent to Go channels with additional inspiration from Clojure core.async. LazyRegister AtomicMarkableReference LockFreeLinkedSet LockFreeStack
<#Concurrent::Promises::Future:0x7fe92c706850 pending> scheduled.resolved? # => false # Value will become available after 0.1 seconds. scheduled.value # => 1 Edge features
Return value is a reference to the actor, the actual actor # is never returned. counter = Counter.spawn(:first, 5) # Tell a message and forget returning self. counter.tell(1) counter << 1 # (First counter now contains 7.) # Send a messages asking for a result. counter.ask(0).value Edge features
do Channel.select do |s| s.take(tick) { |t| puts "tick\n" } s.take(boom) { |t| puts "boom\n" exit } s.default do puts ".\n" sleep 0.05 end end end Default selection