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

Fukuoka.rb_0x100_LT.pdf

 Fukuoka.rb_0x100_LT.pdf

Yusuke Iwaki

May 11, 2022
Tweet

More Decks by Yusuke Iwaki

Other Decks in Technology

Transcript

  1. async/await
    Rubyでどう書く?
    Fukuoka.rb 0x100 LT
    @YusukeIwaki

    View Slide

  2. About @YusukeIwaki
    ● 仕事:天神にある会社で、Railsアプリケーションの開発
    ○ 生まれは富山、大学は京都、1社目は札幌、2018年に福岡に移住
    ● puppeteer-rubyとかplaywright-ruby-clientとか作ってる人
    ○ ブラウザを自動操作するやつ
    ○ Rubyでブラウザ操作するのに、Selenium以外の選択肢を作りたい。

    View Slide

  3. Fukuoka.rb 0x0100回おめでとうございます
    ● Kaigi on Rails 2021登壇のきっかけは、Fukuoka.rbの夜会
    ○ 夜の23時まで開いているRubyコミュニティは多分ここだけ?
    ○ 福岡にゆかりのあるひともないひともつながれるRubyコミュニティは素晴らしい
    ○ 今後もゆるゆるとRubyを盛り上げていきましょう

    View Slide

  4. 本日の話
    async / await
    ってRubyだとどうやって使えばいいの?

    View Slide

  5. JSの有名ライブラリ
    Railsから使いたい!
    →Rubyに移植したい!
    async Promise
    then catch
    Promise.all
    Promise.race
    await

    View Slide

  6. おそらく以下のどっちかが現実解
    ● 思考停止でconcurrent-rubyを使う
    ○ とくにRailsアプリケーションだったら手軽
    ● めっちゃ考えてAsync gemを使う
    ○ シングルスレッド動作で最大限のパフォーマンスが出せる

    View Slide

  7. (memo) ここまで1分

    View Slide

  8. おさらい: RubyのThreadとFiber
    ● Thread→思考停止で、2つ以上の処理を同時に流せる
    ● Fiber→ゆずりあいで、2つ以上の処理をかわりばんこに流す
    ○ 合言葉は Fiber.yield

    View Slide

  9. % ruby fiber.rb
    0
    1
    2
    3
    4
    0
    1
    2
    3
    4
    % ruby thread.rb
    0
    0
    1
    1
    2
    2
    3
    3
    4
    4

    View Slide

  10. async/await in Ruby
    ● 思考停止でconcurrent-rubyを使う
    ○ Threadベース
    ○ とくにRailsアプリケーションだったら手軽
    ● めっちゃ考えてAsync gemを使う
    ○ Fiberベース
    ○ シングルスレッド動作で最大限のパフォーマンスが出せる

    View Slide

  11. (memo) ここまで2分半

    View Slide

  12. concurrent-rubyでの async/await Promise
    Concurrent::PromisesモジュールのFutureを使う。
    https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-
    source/promises.in.md

    View Slide

  13. concurrent-ruby: 2種類のFuture
    Concurrent::Promises.future { ….. ; 123 }
    ● 裏で処理をやって、結果として123を返す
    ● Thread.newと同じ使い勝手で、さらに実行結果をもらえるイメージ
    f = Concurrent::Promises.resolvable_future, f.fulfill(123) , f.reject(err)
    ● スレッドセーフな箱を作って、結果123を別スレッドから入れてもらう
    ● JSのPromiseのような使い勝手

    View Slide

  14. concurrent-ruby: Futureのチェーン、結果待ち受け
    future.then { |result| … ; next_result }.then { |next_result| … }
    ● JSのPromise#then, Promise#catch に限りなく近い
    future.value!
    ● futureの結果をもらう。
    ● futureの結果がまだ出ていない場合には、結果が出るまでブロッキング。
    Concurrent::Promises.zip(future1, future2, …)
    ● 全部の結果が出揃うのを待つ。JSのPromise.allと同じ。

    View Slide

  15. async click(x, y) {
    const point = await clickablePoint(x, y)
    await scrollToPosition(x, y)
    await Promise.all([
    mouse.down(x, y),
    mouse.up(x, y),
    ])
    }
    def click(x, y)
    Concurrent::Promises.future do
    point = clickable_point(x, y).value!
    scroll_to_position(x, y).value!
    Concurrent::Promises.zip(
    mouse.down(x, y),
    mouse.up(x, y),
    ).value!
    end
    end
    concurrent-rubyを使ったasync/awaitっぽい実装
    ※ 擬似コードです

    View Slide

  16. でも気をつけろ!concurrent-rubyはThreadベースだ!

    View Slide

  17. View Slide

  18. View Slide

  19. Async gem
    https://rubykaigi.org/2019/presentations/ioquatix.html

    View Slide

  20. ノンブロッキングI/Oを活用し、IO待ちの間に別の処理を行う
    ● シングルスレッドのFiberベースなので、オーバーヘッドが非常に少ない
    Ruby 2.xで利用するときには
    ノンブロッキングI/Oベースの各種ライブラリを併用する必要がある
    Async gem

    View Slide

  21. Async { … } / Async::Reactor.run { … }
    ● 処理のかたまり(task)を定義する
    ○ JSの async () => { … } と似ている
    ● トップレベルで利用するときと、ネストで利用するときとで挙動が違う
    ○ トップレベルで利用時・・・サブタスクが全部終わるまで待つ
    ○ ネストで利用時・・・サブタスクを作成し、スケジュール
    ○ これも JSの async function呼び出しの挙動と似ている
    Async gemの基本

    View Slide

  22. q = Async::Queue.new, q.dequeue, q.enqueue(hoge)
    ● q.dequeueを呼ぶと、誰かがq.enqueueするまで待つ
    ● q.dequeueよりも先に誰かがenqueue(hoge)していたら、即座にhogeが返る
    ● ほぼPromise
    cond = Async::Condition.new, cond.wait, cond.signal(hoge)
    ● cond.waitを呼ぶと、誰かがcond.signalするまで待つ
    ● cond.waitよりも先に誰かがcond.signal(hoge)しても、signalは捨てられる
    ● 自前でPromiseっぽい何かを作る材料になる
    Async::Barrier, Async::Semaphore
    ● サブタスク全部が終わるまで待つ、サブタスクの同時実行数を制限する
    ● Promise.all 的なものを作る材料になる
    Async gemを使って async/await Promise

    View Slide

  23. async click(x, y) {
    const point = await clickablePoint(x, y)
    await scrollToPosition(x, y)
    await Promise.all([
    mouse.down(x, y),
    mouse.up(x, y),
    ])
    }
    def click(x, y)
    Async do
    point = clickable_point(x, y).wait
    scroll_to_position(x, y).wait
    barrier = Async::Barrier.new
    barrier.async { mouse.down(x,
    y).wait }
    barrier.async { mouse.up(x, y).wait }
    barrier.wait
    end
    end
    Async gemを使ったasync/awaitっぽい実装
    ※ 擬似コードです

    View Slide

  24. でも気をつけろ!Async gemはFiberベースだ!
    ● 思考停止で使うと、並列処理されない(当然)
    ● 不用意にwaitすると、メインスレッドがブロックされ全部が動かなくなる
    ● Ruby 2.7で使いたいならノンブロッキングI/Oベースのライブラリ導入に
    そこそこ対応工数がかかる
    ただ、用途があえば
    ● すんごい速い
    ● すんごい安定
    ○ 「ときどき処理順序がひっくり返ってしまって困る・・・」のような
    Threadベースだと起きる問題がFiberベースでは起きない

    View Slide

  25. (memo) ここまで4分

    View Slide

  26. まとめ:async/await in Rubyの現実解
    ● 思考停止でconcurrent-rubyを使う
    ○ とくにRailsアプリケーションだったらもともと入ってるので手軽
    ○ Threadベースなので、処理順序性の保証とかほぼない
    ● めっちゃ考えてAsync gemを使う
    ○ Fiberベースのシングルスレッド動作で最大限のパフォーマンスが出せる
    ○ JSのasync functionに近い動作
    ○ Ruby 2.7環境で既存のアプリケーションに導入するのは結構たいへん

    View Slide

  27. DEMO

    View Slide