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

Threads Aren't Evil

Threads Aren't Evil

To skip to actual technical content go to slide 71 https://speakerdeck.com/schneems/threads-arent-evil?slide=71 the first slides make no sense without narration.

Okay, so threads are pretty evil.

But they are also useful, and given the right development patterns: not impossible to work with.

In this talk you'll look at some real Ruby libraries where threads were applied to accomplish otherwise impossible tasks. We'll look at re-writing a synchronous library to support parallel execution for performance gains. We'll also talk about the operating system internals of exactly what makes a thread a thread.

If you're not comfortable with the "T" word (threads), this talk is the perfect introduction to practical concurrent programming in Ruby.

Richard Schneeman

September 26, 2017
Tweet

More Decks by Richard Schneeman

Other Decks in Technology

Transcript

  1. Are

  2. Not

  3. BTW

  4. #include <stdio.h> int increment(int x) { return x + 1;

    } int main() { int i = 0; printf("Incremented to: %i\n", increment(i)) }
  5. _main: 100000f40: 55 pushq %rbp 100000f41: 48 89 e5 movq

    %rsp, %rbp 100000f44: 48 83 ec 10 subq $16, %rsp 100000f48: c7 45 fc 00 00 00 00 movl $0, -4(%rbp) 100000f4f: c7 45 f8 00 00 00 00 movl $0, -8(%rbp) 100000f56: 83 7d f8 0a cmpl $10, -8(%rbp) 100000f5a: 0f 8d 1f 00 00 00 jge 31 <_main+3F> 100000f60: 48 8d 3d 43 00 00 00 leaq 67(%rip), %rdi 100000f67: b0 00 movb $0, %al 100000f69: e8 1a 00 00 00 callq 26 100000f6e: 89 45 f4 movl %eax, -12(%rbp) 100000f71: 8b 45 f8 movl -8(%rbp), %eax 100000f74: 83 c0 01 addl $1, %eax 100000f77: 89 45 f8 movl %eax, -8(%rbp) 100000f7a: e9 d7 ff ff ff jmp -41 <_main+16> 100000f7f: 8b 45 fc movl -4(%rbp), %eax 100000f82: 48 83 c4 10 addq $16, %rsp 100000f86: 5d popq %rbp 100000f87: c3 retq
  6. _main: 100000f40: 55 pushq %rbp 100000f41: 48 89 e5 movq

    %rsp, %rbp 100000f44: 48 83 ec 10 subq $16, %rsp 100000f48: c7 45 fc 00 00 00 00 movl $0, -4(%rbp) 100000f4f: c7 45 f8 00 00 00 00 movl $0, -8(%rbp) 100000f56: 83 7d f8 0a cmpl $10, -8(%rbp) 100000f5a: 0f 8d 1f 00 00 00 jge 31 <_main+3F> 100000f60: 48 8d 3d 43 00 00 00 leaq 67(%rip), %rdi 100000f67: b0 00 movb $0, %al 100000f69: e8 1a 00 00 00 callq 26 100000f6e: 89 45 f4 movl %eax, -12(%rbp) 100000f71: 8b 45 f8 movl -8(%rbp), %eax 100000f74: 83 c0 01 addl $1, %eax 100000f77: 89 45 f8 movl %eax, -8(%rbp) 100000f7a: e9 d7 ff ff ff jmp -41 <_main+16> 100000f7f: 8b 45 fc movl -4(%rbp), %eax 100000f82: 48 83 c4 10 addq $16, %rsp 100000f86: 5d popq %rbp 100000f87: c3 retq
  7. _main: 100000f40: 55 pushq %rbp 100000f41: 48 89 e5 movq

    %rsp, %rbp 100000f44: 48 83 ec 10 subq $16, %rsp 100000f48: c7 45 fc 00 00 00 00 movl $0, -4(%rbp) 100000f4f: c7 45 f8 00 00 00 00 movl $0, -8(%rbp) 100000f56: 83 7d f8 0a cmpl $10, -8(%rbp) 100000f5a: 0f 8d 1f 00 00 00 jge 31 <_main+3F> 100000f60: 48 8d 3d 43 00 00 00 leaq 67(%rip), %rdi 100000f67: b0 00 movb $0, %al 100000f69: e8 1a 00 00 00 callq 26 100000f6e: 89 45 f4 movl %eax, -12(%rbp) 100000f71: 8b 45 f8 movl -8(%rbp), %eax 100000f74: 83 c0 01 addl $1, %eax 100000f77: 89 45 f8 movl %eax, -8(%rbp) 100000f7a: e9 d7 ff ff ff jmp -41 <_main+16> 100000f7f: 8b 45 fc movl -4(%rbp), %eax 100000f82: 48 83 c4 10 addq $16, %rsp 100000f86: 5d popq %rbp 100000f87: c3 retq
  8. _main: 100000f40: 55 pushq %rbp 100000f41: 48 89 e5 movq

    %rsp, %rbp 100000f44: 48 83 ec 10 subq $16, %rsp 100000f48: c7 45 fc 00 00 00 00 movl $0, -4(%rbp) 100000f4f: c7 45 f8 00 00 00 00 movl $0, -8(%rbp) 100000f56: 83 7d f8 0a cmpl $10, -8(%rbp) 100000f5a: 0f 8d 1f 00 00 00 jge 31 <_main+3F> 100000f60: 48 8d 3d 43 00 00 00 leaq 67(%rip), %rdi 100000f67: b0 00 movb $0, %al 100000f69: e8 1a 00 00 00 callq 26 100000f6e: 89 45 f4 movl %eax, -12(%rbp) 100000f71: 8b 45 f8 movl -8(%rbp), %eax 100000f74: 83 c0 01 addl $1, %eax 100000f77: 89 45 f8 movl %eax, -8(%rbp) 100000f7a: e9 d7 ff ff ff jmp -41 <_main+16> 100000f7f: 8b 45 fc movl -4(%rbp), %eax 100000f82: 48 83 c4 10 addq $16, %rsp 100000f86: 5d popq %rbp 100000f87: c3 retq
  9. #include <stdio.h> int increment(int x) { return x + 1;

    } int main() { int i = 0; printf("Incremented to: %i\n", increment(i)) }
  10. #include <stdio.h> int increment(int x) { return x + 1;

    } int main() { int i = 0; printf("Incremented to: %i\n", increment(i)) }
  11. #include <stdio.h> int increment(int x) { return x + 1;

    } int main() { int i = 0; printf("Incremented to: %i\n", increment(i)) }
  12. #include <stdio.h> int increment(int x) { return x + 1;

    } int main() { int i = 0; printf("Incremented to: %i\n", increment(i)) }
  13. #include <stdio.h> int increment(int x) { return x + 1;

    } int main() { int i = 0; printf("Incremented to: %i\n", increment(i)) }
  14. Process Control Block Process State Process Number Program Counter Registers

    Memory Limits List of open Files Signal Mask CPU Scheduling info (PCB)
  15. array = Array.new(100) { String.new } t1 = Thread.new do

    array.each {|x| x.prepend("hello") } end t2 = Thread.new do array.each {|x| x << " world" } end t1.join; t2.join puts array
  16. array = Array.new(100) { String.new } t1 = Thread.new do

    array.each {|x| x.prepend("hello") } end t2 = Thread.new do array.each {|x| x << " world" } end t1.join; t2.join puts array
  17. array = Array.new(100) { String.new } t1 = Thread.new do

    array.each {|x| x.prepend("hello") } end t2 = Thread.new do array.each {|x| x << " world" } end t1.join; t2.join puts array
  18. array = Array.new(100) { String.new } t1 = Thread.new do

    uri = URI.parse(“http://example.com/hello") hi = Net::Http.get(uri) array.each {|x| x.prepend(hi) } end t2 = Thread.new do uri = URI.parse(“http://example.com/hello") world = Net::Http.get(uri) array.each {|x| x << world } end t1.join; t2.join puts array
  19. void * rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void

    *data2) { return call_without_gvl(func, data1, ubf, data2, TRUE); } void * rb_thread_call_without_gvl(void *(*func)(void *data), void *data1, rb_unblock_function_t *ubf, void *data2) { return call_without_gvl(func, data1, ubf, data2, FALSE); }
  20. void * rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void

    *data2) { return call_without_gvl(func, data1, ubf, data2, TRUE); } void * rb_thread_call_without_gvl(void *(*func)(void *data), void *data1, rb_unblock_function_t *ubf, void *data2) { return call_without_gvl(func, data1, ubf, data2, FALSE); }
  21. 1309 { 1310 void *val = 0; 1311 -> 1312

    rb_thread_t *th = GET_THREAD(); 1313 int saved_errno = 0; 1314 1315 if (ubf == RUBY_UBF_IO || ubf == RUBY_UBF_PROCESS) { (lldb) call rb_backtrace() from <internal:gem_prelude>:4:in `<internal:gem_prelude>' from <internal:gem_prelude>:4:in `require' from.../rubygems.rb:1363:in `<top (required)>' from.../rubygems/specification.rb:873:in `load_defaults' from.../rubygems/specification.rb:821:in `each_spec' from.../rubygems/specification.rb:743:in `each_gemspec' from.../rubygems/specification.rb:743:in `each' from.../rubygems/specification.rb:744:in `block in each_gemspec' from.../rubygems/specification.rb:744:in `each' from.../rubygems/specification.rb:745:in `block (2 levels) in each_gemspec' from.../rubygems/specification.rb:822:in `block in each_spec' from.../rubygems/specification.rb:1161:in `load' from.../rubygems/specification.rb:1161:in `read' (lldb)
  22. void * rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void

    *data2) { return call_without_gvl(func, data1, ubf, data2, TRUE); } void * rb_thread_call_without_gvl(void *(*func)(void *data), void *data1, rb_unblock_function_t *ubf, void *data2) { return call_without_gvl(func, data1, ubf, data2, FALSE); }
  23. #define GVL_UNLOCK_BEGIN() do { \ rb_thread_t *_th_stored = GET_THREAD(); \

    RB_GC_SAVE_MACHINE_CONTEXT(_th_stored); \ gvl_release(_th_stored->vm); #define GVL_UNLOCK_END() \ gvl_acquire(_th_stored->vm, _th_stored); \ rb_thread_set_current(_th_stored); \ } while(0)
  24. require 'thread' my_queue = Queue.new my_thread = Thread.new do loop

    do job = my_queue.pop job.call end end 10.times { job = Proc.new { puts "hello" } my_queue.push(job) } my_thread.join
  25. my_queue = Queue.new POISON = Object.new my_thread = Thread.new do

    loop do job = my_queue.pop break if job == POISON job.call end end
  26. require 'thread' my_queue = Queue.new POISON = Object.new my_thread =

    Thread.new do loop do job = my_queue.pop break if job == POISON job.call end end 10.times { job = Proc.new { puts "hello" } my_queue.push(job) } my_queue.push(POISON) my_thread.join
  27. You can ignore most of them if you use a

    queue and boss/ worker pattern.
  28. Are

  29. Not