Actor, Thread and me (RubyKaigi2015)

Actor, Thread and me (RubyKaigi2015)

Everybody says. Actor helps to solve the multithreading problem. Actor is awesome. Threads are 💩

Really?

An actor model is "just a model", just like MVC.

Today, Recap what Actor Model is.

40f4d1f2e77078955bd01e9fb4a503ba?s=128

seki at druby.org

December 13, 2015
Tweet

Transcript

  1. 3.

    more slide sponsors ׻׋׃׋׍ծה׷ן٦ׅה!USJDLOPUFT הגַכ剢דׅկ✮㹀ծ甧גגְֶג♴ְׁי!NBNF 5BLBZVLJ,BNJZBNB!UBLLJJ ת׌ֹ׭זְַ׵ծ罋ִתׅ![[BL@KQ E3VCZח״׷ⴓ侔٥8FCفؚٗٓىؚٝ ؔ٦ي爡F#PPL4UPSF IUUQ

    FTUPSFPINTIBDPKQUJUMFT1!IJTBTIJN ׉׸ד׮EFGBVMU@TDPQF⢪ְתַׅ!TVHJOPZ ؔٔآشٕ5ءٍخך5.*9ծ倜㉀ㅷؙٓحثغحؚ׮㥨鐰涪㡰⚥!JHBJHB ׫זׁתծTFLJ眍׾ֶ嚂׃׫ֻ׌ְׁկ䊛䃵歲꥔ך倯כծLBXBTBLJSCծZPLPIBNBSC׾ ֶ鑐׃ֻ׌ְׁկ,ذٖؽך飑ⰅכծTFLJׁ׿穗歋ד׀湱锑ְ׋׌ֽ׷ה׀⼿⸂׃תׅկ!UTVCPJ
  2. 10.

    Actor is better than Thread ϚϧνεϨουͰൃੜ͢Δ໰୊͕ղܾ͢Δ εϨουͰਖ਼͘͠ॻ͘ͷ͸Ή͔͍ͣ͠ ΞΫλʔͳΒ؆୯ʹॻ͚Δ εϨου͸ ຊ౰ʁ

    Everybody says. Actor helps to solve the multithreading problem. Actor is awesome. Threads are . Really?
  3. 11.
  4. 12.

    Actor and Thread εϨουؒͷσʔλͷަ׵Λ੍ݶ ෳ਺ͷεϨου͕σʔλΛॻ͖׵͑ͳ͍ ΞΫλʔͳΒ؆୯ͩ ͑ʁͦ͜ͳͷʁ - Control passing

    data between threads - Properly handle changeing data between threads - So, that's the point?
  5. 13.

    Process and Thread εϨουͱϓϩηεͷҧ͍ ϝϞϦۭؒΛڞ༗Ͱ͖Δ ϝϦοτͰ͋ΓσϝϦοτͰ͋Δ ͭ·ΓͦΕ͕ͦ͜ಛ௃Ͱ͋Δ ϓϩηεͰ͍͍ͷͰ͸ʂ Difference between

    Thread / Process - Could share memory space - So, that's one of the big characteristics of threads Well, couldn't it be process ...!!
  6. 20.

    Ether (Æther) ͔ͭͯޫͷ೾Λ఻͑Δഔ࣭ͱͯ͠Ծ૝͞Ε͍ͯͨ෺࣭ Ethernetͷޠݯ? ͔͍͍͔ͬ͜Βࠓ೔͸Τʔςϧ - Æther used to be

    thought as medium that transmit llight waves - Originated from Ethernet ? - "Æther" sounds good to me, so I'll call Messaging System "Ether" today
  7. 23.

    ͜Ε͚ͩͷಓ۩ͰγεςϜΛ ॻ͜͏ ଞʹActorͷੜ੒΋͋Δ͚ͲׂѪ ےτϨΈ͍ͨͳײ͡ ࢥߟ࣮ݧʁ Let's try to make system

    with these instruments - Skip other topics like Actor Creation - Maybe a thought experiment?
  8. 27.

    Simple Worker def oso_matz_RPC request = ... ether.send(:oso_matz, request) return

    ether.receive end while true request = ether.receive result = do_it(request) ether.send(request.from, result) end
  9. 28.
  10. 30.

    ୭͔ͷ݁ՌͰܭࢉ while true request = ether.receive # jushi_matz RPC ether.send(:choro_matz,

    task1) task1_value = ether.receive result = do_it(request, task1_value) ether.send(request.from, result) end SFDFJWF✳א 2種類のメッセージ When you work with the results
  11. 31.

    2छྨͷϝοηʔδΛѻ͏ 1. Receive 4. Do it 2. Send task1 3.

    Receive task1 5. Send SFDFJWF✳א 2種類のメッセージ 2 types of messages
  12. 32.

    ͋ͱͰ΍Δ pending = [] while true request = pending.shift ||

    ether.receive ether.send(:choro_matz, task1) while true message = ether.receive if message.reply? task1_value = message break end pending << message end result = do_it(request, task1_value) ether.send(request.from, result) end 欲しいものを待つ あとでやるリスト Do it Later - wait for reply message - with list of Do it Later あとでやるリスト
  13. 33.

    ͋ͱͰ΍ΔϦετ 1. Receive 4. Do it 2. Send task1 3.

    Receive task1 5. Send 処理できないやつをあとでやる Stock and process do-it-later strategy
  14. 38.

    Ӆ͞Εͨґଘ͕͋ͬͨ ࣌ʑɺΈΜͳ͕reply଴ͪʹͳΔ ୭΋ࢭ·ͬͯͳ͍͚ͲγεςϜ͸ࢭ·ͬͯΔ Hidden dependency...revealed - All of the workers

    wait for their replies once in a while - Everyone is trying to work, but the system itself stops
  15. 39.

    ·͞ʹnon-blocking ฦ৴ʹݶΒͣෳ਺ͷϝοηʔδΛ଴ͭέʔε Future/PromiseͰͷ݁Ռ໰͍߹Θͤͱ͔ ෳ਺ͷΠϕϯτΛ଴ͬͯॲཧ͢Δͱ͔ (ڞ༗ࢿݯͱ͔΋) This WAS actually "non-blocking" When

    multiple messages, including replies, are held by workers - e.g. Query for the results between Future and Promise - e.g. Start processing when some events finish - Not only messages but the other shared resources
  16. 42.

    ͦ͏͍͏ঢ়گ͕ѱ͍ શͯͷΞΫλʔ͕୭ʹ΋ґଘ͠ͳ͍΂͖ʁ ΋͔ͯ͠͠αϒϧʔνϯͰ͍͍ʁ ͦΕͳΒΞΫλʔͷҙٛ͸... The issue is this situation -

    Every actor should only depend on themselves -- Does subroutine work for this? --- if then, what's the meaning of Actor?
  17. 43.

    ͦ͏͍͏ঢ়گ͕ѱ͍ ਖ਼͘͠ॻ͚͹͏·͍͘͘ͷʹʂ ͍͍ͨͯͦ͏ ʂ - write it the right way,

    and it will work fine! -- This applies to usually everything
  18. 45.

    ͦ͏͍͏ঢ়گΛड͚ೖΕΔ ΞΫλʔ͸Ϟσϧ / ےτϨ / ࢥߟ࣮ݧ Ή͠Ζঢ়گΛड͚ೖΕΑ͏ʂ Accept these situations

    - Actor is a model and a thought experiment -- Accept these difficult situations
  19. 47.

    event driven while true event = ether.receive case event.kind when

    :request do_request(event) when :reply_choro_matz do_reply_choro_matz(event) .... end end
  20. 48.

    ͋͋callbackͶ while true event = ether.receive case event.kind when :request

    do_request(event) when :reply_choro_matz do_reply_choro_matz(event) .... end end callback はこの分岐が 動的になっただけ Ah, callbacks - This branch just turned dynamic for callback
  21. 49.

    ్தܦաΛϝϞ͢ΔҨݴ࡞ઓ def do_request(event) uid = push_context { :from => event.form

    } ether.send(:choro_matz, [args, uid]) end def do_reply_choro_matz(reply) uid = reply.uid context = pop_context(uid) value = do_oso_matz(context, reply) ether.send(context[:from], value) end save context restore context Save-the-progress strategy
  22. 50.

    ॲཧͷྲྀΕΛࣗવʹॻ͚ͳ͍ ίϯςΩετΛཅʹѻΘͳ͖ΌͳΒͳ͍ ॲཧΛࣗવͳྲྀΕͱͯ͠ॻ͖ʹ͍͘ while true request = ether.receive # jushi_matz

    RPC ether.send(:choro_matz, task1) task1_value = ether.receive result = do_it(request, task1_value) ether.send(request.from, result) end 元は単純だったのに Difficult to write the process naturally - It was simple originally... (but become complex)
  23. 53.

    ࢭ·Δ ϒϩοΫ͢Δॲཧ͕͋ͬͨΒશ෦ࢭ·Δ ϒϩοΫͯ͠΋શମ͸ಈ͔͍ͨ͠ ͦ͜ͰϚϧνεϨουͰ͢Αʂ ʢͦ΋ͦ΋Fiber೉͍͠͠ʣ Halt - Halt if there

    is a process that blocks other one - But want to run a whole system if there is - This is good timing to use multi threads
  24. 59.
  25. 61.

    Bartender::Reader#read(n) def read(n) while @buf.bytesize < n chunk = _read(n)

    break if chunk.nil? || chunk.empty? @buf += chunk end @buf.slice!(0, n) end
  26. 63.

    Bartender::Reader#_read(n) def _read(n) @fd.read_nonblock(n) rescue IO::WaitReadable select_readable retry end def

    select_readable @bartender[:read, @fd] = Fiber.current.method(:resume) Fiber.yield ensure @bartender.delete(:read, @fd) end
  27. 64.

    Bartender::Reader#_read(n) def _read(n) @fd.read_nonblock(n) rescue IO::WaitReadable select_readable retry end def

    select_readable @bartender[:read, @fd] = Fiber.current.method(:resume) Fiber.yield ensure @bartender.delete(:read, @fd) end Method object
  28. 65.

    Bartender::Reader#_read(n) def _read(n) @fd.read_nonblock(n) rescue IO::WaitReadable select_readable retry end def

    select_readable @bartender[:read, @fd] = Fiber.current.method(:resume) Fiber.yield ensure @bartender.delete(:read, @fd) end back to Bartender
  29. 66.

    Bartender::Reader#_read(n) def _read(n) @fd.read_nonblock(n) rescue IO::WaitReadable select_readable retry end def

    select_readable @bartender[:read, @fd] = Fiber.current.method(:resume) Fiber.yield ensure @bartender.delete(:read, @fd) end Unregister callback
  30. 69.

    Bartender::Server class Server def initialize(bartender, port, &blk) @bartender = bartender

    @server = TCPServer.new(port) @bartender[:read, @server] = self.method(:on_accept) @blk = blk end def on_accept client = @server.accept reader = Reader.new(@bartender, client) writer = Writer.new(@bartender, client) fiber = Fiber.new do @blk.yield(reader, writer) end fiber.resume end end not important
  31. 70.

    DRbEchoServer def initialize(bartender, port) @rdv = Rdv.new Bartender::Server.new(bartender, port) do

    |reader, writer| begin while true _, msg, argv = req_drb(reader) case msg when 'push' value = @rdv.push(argv) else value = @rdv.pop end reply_drb(writer, true, value) end rescue p $! end end end not important
  32. 71.

    DRbEchoServer def load(reader, marshal=true) sz = reader.read(4) sz = sz.unpack('N')[0]

    data = reader.read(sz) return data unless marshal begin Marshal.load(data) rescue DRb::DRbUnknown.new($!, data) end end def req_drb(reader) ref = load(reader, false) msg = load(reader) argc = load(reader) argv = argc.times.collect { load(reader) } block = load(reader, false) [ref, msg, argv] end ブロックを気にせずreadし まくる Don't care about I/O blocking and context switch Just read it sequentially
  33. 78.

    method_missing class ActorsOffice def initialize(actor) @queue = Queue.new @thread =

    Thread.new(actor) do catch(actor) do while true msg, arg, blk = @queue.pop actor.__send__(msg, *arg, &blk) end end end end def __thread__; @thread; end def method_missing(m, *a, &b) @queue.push([m, a, b]) end end
  34. 82.

    ether = Rinda::TupleSpace _, message = ether.take([:oso_matz, nil]) ether.write([message[:from], {

    :reply => "Hi!" }) message = { :from => :choro_matz, :greeting => "Hello, World." } ether.write([:oso_matz, message]) ether.write([:todo_matz, message])
  35. 83.

    ·͋ಈ͘ dRubyͱ૊Έ߹ΘͤΕ͹ϓϩηεؒ΋ಈ͘Α Α͘࢖ͬͯΔ Yes, it works. - You can run

    it between processes / machines by using dRuby - I use it, frequently