Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Concurrency in Ruby: Tools of the trade

Concurrency in Ruby: Tools of the trade

Concurrency has been a popular topic in the programming community in the latest decade and has received special attention in the Ruby community in the latest years. In this talk, José Valim will showcase the tools we have available in Ruby and in the community today, focusing on common pitfalls. This is a great opportunity to discuss why concurrency matters and how we could move forward.

Plataformatec

June 08, 2013
Tweet

More Decks by Plataformatec

Other Decks in Technology

Transcript

  1. @josevalim / Plataformatec
    Concurrency
    Ruby
    in

    View full-size slide

  2. •Started with Ruby in 2006
    About me

    View full-size slide

  3. •Started with Ruby in 2006
    •Open Source Contributor
    About me

    View full-size slide

  4. •Started with Ruby in 2006
    •Open Source Contributor
    •Joined Rails Core in 2010
    About me

    View full-size slide

  5. •Started with Ruby in 2006
    •Open Source Contributor
    •Joined Rails Core in 2010
    •Author of elixir-lang.org
    About me

    View full-size slide

  6. •Why concurrency?
    Topics

    View full-size slide

  7. •Why concurrency?
    •The problem today
    Topics

    View full-size slide

  8. •Why concurrency?
    •The problem today
    •Tools of the trade
    Topics

    View full-size slide

  9. Why concurrency?

    View full-size slide

  10. Server Server Server Server

    View full-size slide

  11. 50 cores
    $2600

    View full-size slide

  12. It is no longer about
    the future, it is
    about now

    View full-size slide

  13. In Ruby, we talk
    about threads

    View full-size slide

  14. We need our
    applications to be
    thread-safe

    View full-size slide

  15. class User
    def self.current_user=(user)
    @@current_user = user
    end
    def self.current_user
    @@current_user
    end
    end

    View full-size slide

  16. post "/do_something" do
    User.current_user =
    user_from_request
    # some work
    Mailer.deliver_to(
    User.current_user)
    end

    View full-size slide

  17. 1. Assign current_user
    2. Do some work
    3. Get current_user to
    send an e-mail

    View full-size slide

  18. thread 1 current_
    user thread 2

    View full-size slide

  19. thread 1 current_
    user thread 2
    jose

    View full-size slide

  20. thread 1 current_
    user thread 2
    jose
    jose

    View full-size slide

  21. thread 1 current_
    user thread 2
    jose
    jose
    jose

    View full-size slide

  22. thread 1 current_
    user thread 2
    jose
    jose
    jose
    jose

    View full-size slide

  23. thread 1 current_
    user thread 2
    jose
    jose
    matz
    jose
    jose

    View full-size slide

  24. thread 1 current_
    user thread 2
    jose
    jose
    matz
    matz
    jose
    jose

    View full-size slide

  25. thread 1 current_
    user thread 2
    jose
    jose
    matz
    matz
    jose
    jose
    jose

    View full-size slide

  26. thread 1 current_
    user thread 2
    jose
    jose
    matz
    matz
    jose
    jose
    jose
    jose

    View full-size slide

  27. thread 1 current_
    user thread 2
    jose
    jose
    matz
    matz
    jose
    jose
    jose
    jose
    jose

    View full-size slide

  28. thread 1 current_
    user thread 2
    jose
    jose
    matz
    matz
    jose
    jose
    jose
    jose
    jose
    jose

    View full-size slide

  29. thread 1 current_
    user thread 2
    jose
    jose
    matz
    matz
    jose
    jose
    jose
    jose
    jose
    jose

    View full-size slide

  30. User.current_user is
    shared mutable state
    (global)

    View full-size slide

  31. ActionMailer::Base.from
    ActionController::Base.logger
    Devise.password_length
    SimpleForm.form_options

    View full-size slide

  32. What is Ruby?

    View full-size slide

  33. Ruby is a programming
    language with different
    implementations

    View full-size slide

  34. Rails 2.2
    threadsafe

    View full-size slide

  35. Thread-safety means
    different things for
    different implementations

    View full-size slide

  36. hash = {}
    10.times do |i|
    Thread.new {
    hash[i] = "Hello #{rand}"
    }
    end
    Threadsafe?

    View full-size slide

  37. Thread-safe
    YARV Yes
    JRUBY No
    RBX 2.0 No
    Hashes

    View full-size slide

  38. global virtual machine lock (YARV)
    @cached[template] =
    File.read(template)
    static VALUE
    env_aset(VALUE obj,
    VALUE nm, VALUE val)
    {
    ...
    hash.c

    View full-size slide

  39. global virtual machine lock (YARV)
    @cached[template] =
    File.read(template)
    static VALUE
    env_aset(VALUE obj,
    VALUE nm, VALUE val)
    {
    ...
    hash.c

    View full-size slide

  40. C Code
    C2 C3
    C4
    C1

    View full-size slide

  41. C Code
    C2 C3
    C4
    C1

    View full-size slide

  42. C Code
    C2 C3
    C4
    C1

    View full-size slide

  43. C Code
    C2
    C3
    C4
    C1

    View full-size slide

  44. C Code
    C2
    C3
    C4
    C1

    View full-size slide

  45. Without the GVL, other
    Ruby implementations
    may try to change the
    same Hash at the same
    time, corrupting it

    View full-size slide

  46. • Class definitions
    • Instance variables
    • Class variables
    • Data structures
    ...

    View full-size slide

  47. Tools of the trade

    View full-size slide

  48. Thread locals

    View full-size slide

  49. class User
    def self.current_user=(user)
    @@current_user = user
    end
    def self.current_user
    @@current_user
    end
    end

    View full-size slide

  50. class User
    def self.current_user=(user)
    Thread.current[:cu] = user
    end
    def self.current_user
    Thread.current[:cu]
    end
    end

    View full-size slide

  51. Time.zone
    I18n.locale
    ActiveSupport::Notifications.
    instrumenter

    View full-size slide

  52. class Auth
    def initialize(callback)
    @callback = callback
    end
    def call(info)
    @user = user_from_info(info)
    res = @callback.call(info)
    update_user(res) # access @user
    res
    end
    end

    View full-size slide

  53. auth =
    Auth.new -> { puts "Hello" }
    auth.call(id: 13)

    View full-size slide

  54. class Auth
    def initialize(callback)
    @callback = callback
    end
    def call(info)
    @user = user_from_info(info)
    res = @callback.call(info)
    update_user(res) # access @user
    res
    end
    end

    View full-size slide

  55. class AuthMiddleware
    def initialize(callback)
    @callback = callback
    end
    def call(info)
    @user = user_from_info(info)
    res = @callback.call(info)
    update_user(res) # access @user
    res
    end
    end

    View full-size slide

  56. config.middleware.use(
    AuthMiddleware
    )

    View full-size slide

  57. Eager loading

    View full-size slide

  58. class MyApp < Sinatra::Base
    autoload :Broker, "my_app/broker"
    post "/content" do
    # ...
    MyApp::Broker.push(info)
    end
    end
    Threadsafe?

    View full-size slide

  59. module MyApp::Broker
    # ...
    def push do
    # ...
    end
    # ...
    end

    View full-size slide

  60. module MyApp::Broker
    # ...
    def push do
    # ...
    end
    # ...
    end

    View full-size slide

  61. module MyApp::Broker
    # ...
    def push do
    # ...
    end
    # ...
    end

    View full-size slide

  62. Let’s replace all autoloads
    by requires!

    View full-size slide

  63. module MyApp
    extend ActiveSupport::Autoload
    eager_autoload do
    autoload :Broker
    end
    end
    MyApp.eager_load!

    View full-size slide

  64. config.eager_load_namespaces << MyApp

    View full-size slide

  65. hash = {}
    10.times do |i|
    Thread.new {
    hash[i] = "Hello #{rand}"
    }
    end

    View full-size slide

  66. “There are heaps of non
    thread-safe usage of
    Hashes in Rails and the
    surrounding gems”

    View full-size slide

  67. Using hashes as a cache
    is an extremely common
    pattern in Ruby

    View full-size slide

  68. @cached[template] =
    File.read(template)
    Template lookup

    View full-size slide

  69. @controller_names[“posts”] =
    PostsController
    Router

    View full-size slide

  70. Helper
    def presenter_for(model)
    "#{model.class.name}Presenter".
    constantize.new(model)
    end
    presenter_for(Post.new)
    #=> PostPresenter<@post=...>

    View full-size slide

  71. @@p = Hash.new do |h, k|
    h[k] = "#{k}Presenter".constantize
    end
    def presenter_for(model)
    @@p[model.class.name].new(model)
    end

    View full-size slide

  72. hash = {}
    10.times do |i|
    Thread.new {
    hash[i] = "Hello #{rand}"
    }
    end

    View full-size slide

  73. hash = {}
    mutex = Mutex.new
    10.times do |i|
    Thread.new {
    mutex.synchronize {
    hash[i] = "Hello #{rand}"
    }
    }
    end

    View full-size slide

  74. Hash
    C2 C3
    C4
    C1

    View full-size slide

  75. Hash
    C2 C3
    C4
    C1

    View full-size slide

  76. Hash
    C2 C3
    C4
    C1

    View full-size slide

  77. Hash
    C2
    C3
    C4
    C1

    View full-size slide

  78. Hash
    C2
    C3
    C4
    C1

    View full-size slide

  79. Thread safe gem
    https://github.com/headius/thread_safe

    View full-size slide

  80. ThreadSafe::Hash.new
    ThreadSafe::Array.new

    View full-size slide

  81. Better tools
    of the trade

    View full-size slide

  82. live_assets#sse
    Manager
    SSE
    Subscriber
    live_assets#sse
    SSE
    live_assets#sse
    SSE
    Subscriber
    app/assets vendor/assets
    lib/assets
    Operating System Notifications

    View full-size slide

  83. class Manager
    def initialize
    @subscribers = []
    end
    def subscribe(s)
    @subscribers << s
    end

    View full-size slide

  84. def unsubscribe(s)
    @subscribers.delete(s)
    end
    def notify(event)
    @subscribers.each do |s|
    s << event
    end
    end
    end

    View full-size slide

  85. live_assets#sse
    Manager
    SSE
    Subscriber
    live_assets#sse
    SSE
    live_assets#sse
    SSE
    Subscriber
    app/assets vendor/assets
    lib/assets
    Operating System Notifications

    View full-size slide

  86. class Manager
    attr_reader :queue
    def initialize
    @subscribers = []
    @queue = Queue.new
    end
    def loop
    while message = @queue.pop
    event, *args = message
    send(event, *args)
    end
    end

    View full-size slide

  87. manager = Manager.new
    Thread.new { manager.loop }
    manager.queue <<
    [:notify, :reloadCSS]

    View full-size slide

  88. We need abstractions
    that can move us away
    from shared mutable state

    View full-size slide

  89. github.com/celluloid

    View full-size slide

  90. We have learned about
    different tools to tackle
    thread safety issues

    View full-size slide

  91. thread.rb
    • Thread
    • Mutex
    • ConditionVariable
    • Queue
    • SizedQueue

    View full-size slide

  92. Gems
    • atomic
    • thread_safe

    View full-size slide

  93. The different semantics
    existing in different
    implementations makes
    concurrency in Ruby harder

    View full-size slide

  94. We need to push
    for better semantics
    at the same time we change
    how we think about concurrency

    View full-size slide

  95. Thank you!
    @josevalim / Plataformatec

    View full-size slide