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

Ruby Threads

Ruby Threads

Luong Vo

June 14, 2019
Tweet

More Decks by Luong Vo

Other Decks in Programming

Transcript

  1. Thread == Thread of execution != Hardware thread Provides context

    for CPU Can be scheduled independently 4 4
  2. A thread is a basic unit of CPU utilization, which

    comprises a thread ID, a program counter, a register set, and a stack. 5 5
  3. 4 core CPU can genuinely support 4 hardware threads at

    once ‑ the CPU really is doing 4 things at the same time 8 8
  4. Then we are all limited to 4 threads ?? You

    are probably using Macbook with a Core i5‑8259U... 9 9
  5. 1. Intel® Hyper‑Threading Technology 2. Hardware thread can run multiple

    software threads 3. Hardware thread ‑ OS / Software thread ‑ Language 11 11
  6. Thread in Ruby # Initialization thr = Thread.new { puts

    "Whats the big deal" } # status thr.status # => "sleep", "running", 'etc' # other operations thr.kill # should not be used ‑> raise exception thr.exit thr.stop && thr.wakeup thr.pass thr.join 16 16
  7. Thread.main == Thread.current # Your Ruby programm is always running

    in a main thread. # When the main thread exits, all other threads are # immediately terminated and the process exits. 17 17
  8. Maximum number of threads 10_000.times do |i| begin Thread.new {

    sleep } rescue ThreadError puts "Your thread limit is #{i} threads" Kernel.exit(true) end end # it depends, mine is: 4094 18 18
  9. Ruby maps its threads to OS native threads 100.times do

    Thread.new { sleep } end puts Process.pid # returns 26600 sleep # run in console: `top ‑l1 ‑pid 26600 ‑stats pid,th` 20 20
  10. Benefits of native threads Run on multiple processors Scheduled by

    the OS Blocking IO operations don't block other threads 22 22
  11. Race condition Occurs when two or more threads can access

    shared data and try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don't know the order in which the threads will attempt to access the shared data. 25 25
  12. files = { 'daddario.png' => '*image data*', 'elixir.png' => '*image

    data*' } expected_count = 2 100.times do uploader = FileUploader.new(files) uploader.upload actual_size = uploader.results.size if actual_size != expected_count raise("Race condition, size = #{actual_size}") end end puts 'No race condition this time' exit(0) 27 27
  13. def results # Threads share AST, so here might be

    a # race condition (one thread creates Queue # then another one creates it again) # To fix: move assignment to #initialize @results ||= Queue.new end Threads share an address space: context, variable and AST (code tree). 28 28
  14. Good practices 1. Avoid lazy instantiation in multi‑ thread environment

    2. Avoid concurrent modifications 3. Protect concurrent modifications 29 29
  15. Facts 1. MRI allows concurrency but prevents parallelism 2. Every

    Ruby process and process fork has its own GIL 3. MRI releases GIL when thread hits blocking IO (HTTP request, console IO, etc.). Therefore, blocking IO could run in parallel 4. Other implementations (JRuby) don't have GIL 34 34
  16. Reasons of GIL 1. Protect Ruby internal C code from

    race conditions (it is not always thread safe) 2. Protect calls to C extensions API 3. Protect developers from race conditions 35 35
  17. There always will be a sweet spot between utilization and

    context switching and it is important to find it 38 38
  18. Computations‑ rich code on MRI runs better on 1 thread

    while on other implementations on N = CPU cores thread 39 39
  19. Thread safe codes: 1. Doesn't corrupt your data 2. Leaves

    your data consistent 3. Leaves semantics of your program correct 42 42
  20. Example: Order a product Order = Struct.new(:amount, :status) do def

    pending? status == 'pending' end def collect_payment puts "Collecting payment..." self.status = 'paid' end end order = Order.new(100.00, 'pending') 5.times.map do Thread.new do if order.pending? # check order.collect_payment # set end end end.each(&:join) 43 43
  21. Mutex Mutual exclusion, guarantees that no two threads enter the

    critical section of code at the same time Until the owning thread unlocks the mutex, no other thread can lock it The guarantee comes from the OS 45 45
  22. Mutex tips Use mutex to read value. Remember: mutexes prevents

    parallel execution of critical sections. Make critical sections as small as possible. Deadlock may occur when one thread waiting for a mutex locked by another thread waiting itself for the first one. Use mutex#try_lock 46 46
  23. Why at the same time? Thread is just a terminology.

    The ultimate reason for its existence is our demand. 51 51
  24. Beyond Ruby Threads 1. Popular application & ecosystem (Bundler, Puma,

    Phusion Passenger,...) 2. Concurrency Models (Actors, CSP, STM, Guilds,...) 3. Concurrency Patterns (ThreadPools, Reactor,...) 4. Other languages concurrency implementations (Golang, Erlang,...) 55 55