2023年8月8日に「GO TechTalk #21 並列処理をGo/Rust/Kotlin/Python/JSで解説!思想の違いを体感しよう」(connpass)を開催しました。
本記事では当日の内容を簡単に紹介します。
GO TechTalkは、GO株式会社のエンジニアたちが、タクシーアプリ『GO』をはじめとしたサービスやプロダクトを開発する中で得た技術的ナレッジを共有するイベントです。
GO株式会社には、タクシーアプリ『GO』のユーザアプリや車載アプリ、次世代AIドラレコサービス『DRIVE CHART』、それらを支えるバックエンドAPIやAI基盤など、さまざまなプロダクトがあります。それらはプロダクトの特性に応じて Go、Python、Kotlin、JavaScript、Rust などさまざまな言語で実装しています。
今回のイベントでは、テーマを「並列処理および並行処理の手法」として、5つのプログラミング言語に関する解説を行いました。この度は1000人以上からお申し込みいただき、イベント当日には多くの方から感想や質問を受け取りました。当日にお答えすることができなかった質問についても、本記事にて回答しております。参加できなかった方はもちろん、参加された方もぜひご一読ください。
こちらのツイートのスレッドで当日の様子や雰囲気を感じていただけると思います。
GO TechTalk #21 「並列処理をGo/Rust/Kotlin/Python/JSで解説!思想の違いを体感しよう」
— GO Inc. dev | タクシーアプリ『GO』 (@goinc_techtalk) August 8, 2023
はじまります!
ライブ配信:https://t.co/221cAgF0E5
イベント概要:https://t.co/NZuK99FlIf#goinc_tech_talk
今回はこちらのメンバーが登壇しました。
GO株式会社は技術書典10の回からスポンサーとして協賛しております。さらに技術書典11からは有志によるサークル参加も始め、これまでに4冊の技術同人誌を頒布してきました。
今回のイベントは、技術書典12および13で頒布した技術同人誌をもとに構成しました。該当のPDFは技術書典マーケットプレイスで無料配布中ですので、ぜひご一読ください!
※ 各書籍には頒布した当時の情報が収められています。書籍中の Mobility Technologies は GO株式会社の旧社名です。
またイベント中に「技術書典に出る時このテーマはどうやって決めたんですか?」という質問をいただきました。社内に技術書典企画グループがあり、そこで今回の企画はどうしようかと練っています。今回は、一人の言語比較できたら面白そうというアイディアがきっかけになりました。 (動画 / スライド)
それぞれの言語パートに入る前に、前提知識として以下3つについて紹介しました。
Go編では、タクシーアプリ『GO』のバックエンドで行っている車両のGPS座標データをDBに取り込むストリーミング処理を例に、Goルーチンとチャネルを利用した並列処理の基本的な実装方法を紹介しました。さらに、よく発生するトラブルとして、期待したスループットが得られないチャネル詰まりが起こったときの対処法についても紹介しています。 (動画 / スライド)
オプションは特に用意されておらず、Goランタイムに委ねられる言語仕様となっています。経験上、1プロセスに対するCPUの割り当てを増やすよりも、複数のコンテナを立ち上げてプロセス数を増やし、それによってスケールする方法で解決しています。
書籍『Go言語による並行処理』の第6章に、GoランタイムがGoルーチン実行をどのように行うか動きを扱った章がありました。OSスレッドとの比較はされていませんが、OSスレッドを切り替えることなくスレッドとメモリに載っている呼び出し元の処理にGoランタイムが戻すことができるケースが上げられています。
チャネルをクローズすると、データを新たに追加できないが、空になるまで取り出し続けられる機能は提供されているので、それを用いてGraceful Shutdownを実装しています。
社名は「GO Inc.(ゴーインク)」、アプリは「アプリのGO」や「GOアプリ」、言語は「Go言語」や「Golang(ゴーラング)」などと呼ぶようにして区別しています。
はい、OSのプロセス、スレッドの説明では、CPUコアに割り当てる処理はOSのカーネルがやっているという意図でお話ししました。
ひとえに並列処理のロジックと言っても、排他制御とか、チャネルをどう組むか等は考える必要があります。あくまで、並列処理を行うGoルーチンの実行スケジューリングと、同期処理、非同期処理の使い分けをやってくれるという理解が正しいです。常にCPUを使うわけではないI/Oや通信の処理と、CPUを使う処理のGoルーチンを混ぜて実行しても、CPUを効率良くつかって並列処理を実現してくれます。
PythonにはGIL(Global Interpreter Lock)という機構があり、これによりプロセス内で同時に実行可能なバイトコードのスレッドは1つに制限されるため、並列処理は得意ではありません。このPython編では、ProcessPoolExecutorを使用して複数のプロセスを起動することで並列処理を行う手法や、その際のTips、そして並列化に伴う落とし穴を紹介しました。 (動画 / スライド)
もともとPythonで直列処理として実装されていた遅い処理を即時的に並列化したい場合、Python内部での並列化が良いと思います。ただ複雑なシチュエーションならPythonの外側で並列化を試みるのが良いと思います。
典型的なのはDBコネクションだと思います。
ミドルウェア環境があるのなら、最初からそちらに載せるのもいいと思います。とはいえ「ちょっとしたデータ分析」から使うのは準備が重いかもしれません。
プロセス作成のコストは今のCPUの処理速度から見ると結構重いと思います。タスクの処理時間が1秒切るようなケースだと割に合わないので困ると思います。なのでPythonの並列処理は使えるのは1回の処理が数分かかるようなケースからかなと思っています。
ProcessPoolExecutorは初期化時に使うプロセスを最初に全部作りその後使い回します。なのでずっとプロセス作成が負担になるわけではないですが、処理量が少ない、全体で数秒で終わるものを早くする目的には向きません。反対に処理量が多く、特に1つの処理で数分以上かかるようなケースで使うのがおすすめです。
Rust編では、スレッド間でのデータ共有が不要な場合と、必要な場合、それぞれの並列処理に対して具体的なコードを交えて紹介しました。またMutexとArcを用いてスレッド間のデータ共有を安全に行う方法についてもサンプルコードを交えながら紹介しています。 (動画 / スライド)
概念は同じですが、性質はGoとRustでは異なると思います。
複数スレッド間の通信はコンパイルが通れば上手いことやってくれます。複数プロセスはOS側の話なのでRust単体では安全性は担保できないと思います。
JavaScript編では、シングルスレッドで動作するJavaScriptの非同期実行の進化を紹介しました。標準化前のコールバック処理を駆使していた時代から、Promise、そしてasync/awaitへと進化していく過程を取り上げています。それぞれの段階でのサンプルコードも示し、コードが段階的にシンプルになっていく様子が視覚的に理解しやすいと思います。 (動画 / スライド)
現行もサポートされているモダンブラウザであれば利用できると思います。
特に思い至らないのですが、CPUリソースを大量に使うようなユースケースがある場合は、注意点や問題が生じる可能性はあると思います。
Kotlin編では、⾮同期処理の基礎となる Coroutine について紹介しました。Coroutine はOSが提供するスレッドではなく、いわゆるグリーンスレッドと呼ばれるランタイムがスケジューリングするスレッドです。この Coroutine がどうやって動いているかコードを交えて紹介しています。 (動画 / スライド)
CoroutineScope を使うことで、Coroutine をアクティビティなどのライフサイクルに紐づけることができるので、画面の切り替わりに合わせて Coroutine をキャンセルできます。
KotlinにはGo編で紹介されたチャネルのような仕組みも存在しますが、より簡単に扱えるAPIが提供されています。他にも多くのAPIが提供されており、個人的な印象ですが、Androidアプリの開発を楽にしていくためにいろいろなAPIが追加されているのかなと思います。
GO TechTalk は不定期開催しています。過去の開催レポートは こちら にもありますので、ぜひご覧ください!
GO株式会社の最新技術情報は公式Twitterアカウント @goinc_techtalkで随時発信していきますので、ぜひフォローして続報をお待ちください!
興味のある方は 採用ページ も見ていただけると嬉しいです。