Concurrency, parallelism,
and related mind fuckery

concepts.

@_______kim
T1

Time

T2
# sequential.rb

require 'benchmark'

Benchmark.bm do |x|
    x.report 'sequential:' do
        10_000_000.times { 2 + 2 }
    end
end
> ruby sequential.rb
       user     system      total        real
   0.620000   0.000000   0.620000 (  0.618819)

Processes

fork do
    # things
end

# processes.rb

require 'benchmark'

Benchmark.bm do |x|
    x.report 'sequential:' do
        10_000_000.times { 2 + 2 }
    end

    x.report ' processes:' do
        Process.fork { 5_000_000.times { 2 + 2 } }
        Process.fork { 5_000_000.times { 2 + 2 } }
        Process.wait
    end
end
> ruby processes.rb
       user     system      total        real
sequential:  0.650000   0.000000   0.650000 (  0.640821)
 processes:  0.000000   0.000000   0.310000 (  0.319911)

Threads

# threads.rb

require 'benchmark'

Benchmark.bm do |x|
    x.report 'sequential:' do
        10_000_000.times { 2 + 2 }
    end

    x.report '   threads:' do
        a = Thread.new { 5_000_000.times { 2 + 2 } }
        b = Thread.new { 5_000_000.times { 2 + 2 } }
        [a, b].each(&:join)
    end
end
> ruby threads.rb
       user     system      total        real
sequential:  0.620000   0.000000   0.620000 (  0.622752)
   threads:  0.620000   0.000000   0.620000 (  0.622439)

Global Interpreter Lock

(GIL)

# threads_redux.rb

require 'benchmark'

Benchmark.bm do |x|
    x.report('sequential:') do
        sleep 1
        sleep 1
    end

    x.report('   threads:') do
        a = Thread.new { sleep 1 }
        b = Thread.new { sleep 1 }
        [a, b].map(&:join)
    end
end
> ruby threads_redux.rb
       user     system      total        real
sequential:  0.000000   0.000000   0.000000 (  2.000341)
   threads:  0.000000   0.000000   0.000000 (  1.000932)

Fibers

fiber = Fiber.new do |a|
    # Do some things
    b = Fiber.yield a + 2
    # Something else
    c = Fiber.yield b + 3
    # And again...
    c + 4
end
puts fiber.resume 10 # a
#=> 12
puts fiber.resume 14 # b
#=> 17
puts fiber.resume 16 # c
#=> 20
puts fiber.resume 18
#=> FiberError: dead fiber called
  • Full-featured event loop backed by epoll, kqueue, IOCP, event ports.

  • Asynchronous TCP and UDP sockets

  • Asynchronous DNS resolution

  • Asynchronous file and file system operations

  • File system events

  • ANSI escape code controlled TTY

  • IPC with socket sharing, using Unix domain sockets or named pipes (Windows)

  • Child processes

  • Thread pool

  • Signal handling

  • High resolution clock

  • Threading and synchronization primitives

require 'libuv'
reactor do |reactor|
    # Default reactor

    reactor.timer {
        puts "5 seconds passed"
    }.start(5000)

    puts "this will print first"
end

puts "reactor stopped. No more IO to process"

Promises

# :: Proc -> Promise
reactor.work
reactor do |reactor|
    # Perform some work on the thread pool
    reactor.work {
        2 + 2
    }.then { |result|
        puts "result using a promise #{result}"
    }.catch { |error|
        puts "oh noes!"
    }
end
t1 = reactor.work { ... }
t2 = reactor.work { ... }
reactor.any(t1, t2).then do |x|
    # ...
end
reactor.all(t1, t2).then do |a, b|
    c = a + b
end

Futures

a = t1.value
b = t2.value
c = a + b
t1 = reactor.work { ... }
t2 = reactor.work { ... }
x = reactor.work { ... }

begin
    puts "the thing was #{x.value}"
rescue => error
    puts "oh noes!"
end
acaprojects.com/jobs

Concurrent Ruby

By Kim Burgess