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 Slide

  2. About me

    View Slide

  3. •Started with Ruby in 2006
    About me

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  7. View Slide

  8. Topics

    View Slide

  9. •Why concurrency?
    Topics

    View Slide

  10. •Why concurrency?
    •The problem today
    Topics

    View Slide

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

    View Slide

  12. Why concurrency?

    View Slide

  13. Server Server Server Server

    View Slide

  14. 50 cores
    $2600

    View Slide

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

    View Slide

  16. Server

    View Slide

  17. In Ruby, we talk
    about threads

    View Slide

  18. We need our
    applications to be
    thread-safe

    View Slide

  19. The problem

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. thread 1 current_
    user thread 2

    View Slide

  24. thread 1 current_
    user thread 2
    jose

    View Slide

  25. thread 1 current_
    user thread 2
    jose
    jose

    View Slide

  26. thread 1 current_
    user thread 2
    jose
    jose
    jose

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. What is Ruby?

    View Slide

  38. Ruby is a programming
    language with different
    implementations

    View Slide

  39. Rails 2.2
    threadsafe

    View Slide

  40. Thread-safety means
    different things for
    different implementations

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. C Code
    C2 C3
    C4
    C1

    View Slide

  46. C Code
    C2 C3
    C4
    C1

    View Slide

  47. C Code
    C2 C3
    C4
    C1

    View Slide

  48. C Code
    C2
    C3
    C4
    C1

    View Slide

  49. C Code
    C2
    C3
    C4
    C1

    View Slide

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

    View Slide

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

    View Slide

  52. Tools of the trade

    View Slide

  53. Thread locals

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. 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 Slide

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

    View Slide

  59. 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 Slide

  60. 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 Slide

  61. config.middleware.use(
    AuthMiddleware
    )

    View Slide

  62. Eager loading

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. Let’s replace all autoloads
    by requires!

    View Slide

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

    View Slide

  69. config.eager_load_namespaces << MyApp

    View Slide

  70. Mutex

    View Slide

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

    View Slide

  72. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  81. Hash
    C2 C3
    C4
    C1

    View Slide

  82. Hash
    C2 C3
    C4
    C1

    View Slide

  83. Hash
    C2 C3
    C4
    C1

    View Slide

  84. Hash
    C2
    C3
    C4
    C1

    View Slide

  85. Hash
    C2
    C3
    C4
    C1

    View Slide

  86. View Slide

  87. View Slide

  88. View Slide

  89. View Slide

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

    View Slide

  91. ThreadSafe::Hash.new
    ThreadSafe::Array.new

    View Slide

  92. Better tools
    of the trade

    View Slide

  93. Queues

    View Slide

  94. View Slide

  95. 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 Slide

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

    View Slide

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

    View Slide

  98. 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 Slide

  99. 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 Slide

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

    View Slide

  101. C2 C3
    C4
    C1

    View Slide

  102. C2 C3
    C4
    C1

    View Slide

  103. C2 C3
    C4
    C1

    View Slide

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

    View Slide

  105. github.com/celluloid

    View Slide

  106. Conclusion

    View Slide

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

    View Slide

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

    View Slide

  109. Gems
    • atomic
    • thread_safe

    View Slide

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

    View Slide

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

    View Slide

  112. Thank you!
    @josevalim / Plataformatec

    View Slide