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

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.

seki at druby.org

December 13, 2015
Tweet

More Decks by seki at druby.org

Other Decks in Programming

Transcript

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

    Everybody says. Actor helps to solve the multithreading problem. Actor is awesome. Threads are . Really?
  3. Actor and Thread εϨουؒͷσʔλͷަ׵Λ੍ݶ ෳ਺ͷεϨου͕σʔλΛॻ͖׵͑ͳ͍ ΞΫλʔͳΒ؆୯ͩ ͑ʁͦ͜ͳͷʁ - Control passing

    data between threads - Properly handle changeing data between threads - So, that's the point?
  4. 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 ...!!
  5. 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
  6. ͜Ε͚ͩͷಓ۩ͰγεςϜΛ ॻ͜͏ ଞʹActorͷੜ੒΋͋Δ͚ͲׂѪ ےτϨΈ͍ͨͳײ͡ ࢥߟ࣮ݧʁ Let's try to make system

    with these instruments - Skip other topics like Actor Creation - Maybe a thought experiment?
  7. 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
  8. ୭͔ͷ݁ՌͰܭࢉ 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
  9. 2छྨͷϝοηʔδΛѻ͏ 1. Receive 4. Do it 2. Send task1 3.

    Receive task1 5. Send SFDFJWF✳א 2種類のメッセージ 2 types of messages
  10. ͋ͱͰ΍Δ 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 あとでやるリスト
  11. ͋ͱͰ΍ΔϦετ 1. Receive 4. Do it 2. Send task1 3.

    Receive task1 5. Send 処理できないやつをあとでやる Stock and process do-it-later strategy
  12. Ӆ͞Εͨґଘ͕͋ͬͨ ࣌ʑɺΈΜͳ͕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
  13. ·͞ʹ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
  14. ͦ͏͍͏ঢ়گ͕ѱ͍ શͯͷΞΫλʔ͕୭ʹ΋ґଘ͠ͳ͍΂͖ʁ ΋͔ͯ͠͠αϒϧʔνϯͰ͍͍ʁ ͦΕͳΒΞΫλʔͷҙٛ͸... 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?
  15. ͦ͏͍͏ঢ়گ͕ѱ͍ ਖ਼͘͠ॻ͚͹͏·͍͘͘ͷʹʂ ͍͍ͨͯͦ͏ ʂ - write it the right way,

    and it will work fine! -- This applies to usually everything
  16. ͦ͏͍͏ঢ়گΛड͚ೖΕΔ ΞΫλʔ͸Ϟσϧ / ےτϨ / ࢥߟ࣮ݧ Ή͠Ζঢ়گΛड͚ೖΕΑ͏ʂ Accept these situations

    - Actor is a model and a thought experiment -- Accept these difficult situations
  17. 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
  18. ͋͋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
  19. ్தܦաΛϝϞ͢ΔҨݴ࡞ઓ 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
  20. ॲཧͷྲྀΕΛࣗવʹॻ͚ͳ͍ ίϯςΩετΛཅʹѻΘͳ͖ΌͳΒͳ͍ ॲཧΛࣗવͳྲྀΕͱͯ͠ॻ͖ʹ͍͘ 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)
  21. ࢭ·Δ ϒϩοΫ͢Δॲཧ͕͋ͬͨΒશ෦ࢭ·Δ ϒϩοΫͯ͠΋શମ͸ಈ͔͍ͨ͠ ͦ͜ͰϚϧνεϨουͰ͢Αʂ ʢͦ΋ͦ΋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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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])
  32. ·͋ಈ͘ dRubyͱ૊Έ߹ΘͤΕ͹ϓϩηεؒ΋ಈ͘Α Α͘࢖ͬͯΔ Yes, it works. - You can run

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