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

Javaで最強 コンカレントプログラミング/concurrent-programming-in...

Javaで最強 コンカレントプログラミング/concurrent-programming-in-java

OCHaCafe Season 6 #6 で使用した資料です。

oracle4engineer

February 09, 2023
Tweet

More Decks by oracle4engineer

Other Decks in Technology

Transcript

  1. Javaで最強! コンカレントプログラミング Oracle Cloud Hangout Café – Season 6 #6

    Shuhei Kawamura Cloud Solution Engineer Solutions Architect, Oracle Corporation Japan February 8, 2022
  2. Copyright © 2023, Oracle and/or its affiliates 2 • 所属

    • 日本オラクル株式会社 • ソリューション・アーキテクト本部 • 担当領域 • Cloud Native, App Dev • Data Platform(Spark 周り) • AI Services • Search Service with OpenSearch • (趣味) 認証・認可関連 • コミュニティ • OCHaCafe • CloudNative Days – Observability 川村 修平 (Shuhei Kawamura) @shukawam Twitter/GitHub/Qiita
  3. Copyright © 2023, Oracle and/or its affiliates 3 Agenda 1.

    Warming up 2. Virtual Threads Overview 3. Helidon & Helidon Níma
  4. Copyright © 2023, Oracle and/or its affiliates 5 java.lang.Thread を継承して、run()

    メソッドをオーバーライドする 実行は、Thread.start() で行う Java でスレッドを扱う方法アレコレ 1/3 java.lang.Thread, java.lang.Runnable public class SimpleThread extends Thread { @Override public void run() { System.out.println(String.format("[%s]: %s", Thread.currentThread().getName(), "Hello world!")); } } SimpleThread thread = new SimpleThread(); thread.start();
  5. Copyright © 2023, Oracle and/or its affiliates 6 java.lang.Thread のコンストラクタに

    java.lang.Runnable インターフェースの実装を渡す もちろん、ラムダ式でもOK Java でスレッドを扱う方法アレコレ 2/3 java.lang.Thread, java.lang.Runnable public class SimpleRunnable implements Runnable { @Override public void run() { System.out.println(String.format("[%s]: %s", Thread.currentThread().getName(), "Hello world!")); } } public static void main(String[] args) { System.out.println(String.format("[%s]: %s", Thread.currentThread().getName(), "Hello world!")); Thread thread = new Thread(() -> { System.out.println(String.format("[%s]: %s", Thread.currentThread().getName(), "Hello world!")); }); thread.start(); } Thread thread = new Thread(new SimpleRunnable()); thread.start();
  6. Copyright © 2023, Oracle and/or its affiliates 7 • Concurrency

    Utilities: 並行処理でよく使われるユーティリティ・パッケージ • スレッドプール(※後述)によるスレッド管理や Callable/Future などの非同期処理の結果を取得する仕組み等を提供 • java.lang.Thread をそのまま用いるよりも安全かつ抽象度の高い実装が可能となるので、通常はこちらを使う Java でスレッドを扱う方法アレコレ 3/3 java.util.concurrent.* ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); Future<String> result = singleThreadPool.submit(new SimpleCallableTask()); System.out.println(result.get()); singleThreadPool.shutdown(); if (!singleThreadPool.awaitTermination(1, TimeUnit.SECONDS)) { singleThreadPool.shutdownNow(); } public class SomeCallableTask implements Callable<String> { @Override public String call() throws Exception { return "Hello world!"; } }
  7. Copyright © 2023, Oracle and/or its affiliates 8 複数のスレッドをあらかじめ作成して待機させておく タスクが来たら待っているスレッドにタスクを割り当てて処理を開始させる

    実行させるスレッドに空きがない場合は、タスクをキューに入れてスレッドに空きができるまで待つ java.util.concurrent.ExecutorService というインターフェースを持った様々なスレッドプールの仕組みを提供している • AbstractExecutorService, ForkJoinPool, ScheduledThreadPoolExecutor, ThreadPoolExecutor 通常は Executors というユーティリティクラスを使って ExecutorService インターフェースのインスタンスを取得する • Executors.newSingleThreadExecutor() • Executors.newCachedThreadPool() • Executors.newFixedThreadPool(int nThreads) • Executors.newScheduledThreadPool(int corePoolSize) • etc. スレッドプール 複数のタスクを同時実行する際に、複数のスレッドを効率よく使う仕組み
  8. Copyright © 2023, Oracle and/or its affiliates 9 • work-stealing

    アルゴリズムを実装したスレッドプール • work-stealing: 自分のタスクが終了したワーカースレッドが別のスレッドのタスクキューからタスクを盗む(steal)よ うなスケジューリング方式 • スレッドが遊んでいる時間を極力減らし、並行処理の密度をあげることを狙いとしている 参考:ForkJoinPool task sub-task 1 sub-task 2 sub-task 3 ・・・ ・・・ result fork join ワーカースレッド ワーカースレッド fork join steal タスクキュー タスクキュー タスクキューが空なら 別のワーカーのタスクキュー からタスクを盗む(steal) work-stealing の概要イメージ Fork/Join の概要イメージ
  9. Copyright © 2023, Oracle and/or its affiliates 10 1. java.lang.Thread,

    java.lang.Runnable を使う 2. java.util.concurrent.* を使う Demo1: Java でスレッドを扱う
  10. Copyright © 2023, Oracle and/or its affiliates 11 • Java

    5 で追加された Concurrency Utilities を拡張する形で策定 • Java/Jakarta EE のコンテナがスレッドを含む各種リソースを管理する関係上、アプリケーションから Thread, Concurrency Utilities を直接使うことは非推奨 • → 生成するスレッドを Java/Jakarta EE コンテナ、アプリケーションサーバーの管理下にするために拡張 • Java/Jakarta EE アプリケーション実行環境から非同期に複数の処理を並行実行することを可能にする機能 • Java SE で利用できる ExecutorService, ScheduledExecutorService を拡張したスレッド管理機能を提供 • ManagedExecutorService: 標準的なスレッドプールを提供 • ManagedScheduledExecutorService: タスクの繰り返しやタイマー実行が可能なスレッドプールを提供 • ManagedThreadFactory: コンテナ管理用のスレッドを生成するファクトリー • ContextService: 並行タスクにコンテナの情報を設定するためのユーティリティ 参考:https://jakarta.ee/specifications/concurrency/2.0/concurrency-spec-2.0.html#goals-of-this-specification Jakarta Concurrency/Concurrency Utilities for Java EE
  11. Copyright © 2023, Oracle and/or its affiliates 12 初期の Solaris

    における Java のスレッドの仕組み • 複数の JVM スレッドが 1 OS スレッドに紐づく • = グリーンスレッド • JVM スレッド:OS スレッド= M:1 の関係 • シングルコアの時代にマルチスレッドを扱うための仕 組み Java 18 までの Java のスレッドの仕組み • 1 つの JVM スレッドが 1 OS スレッドに対して厳密に 紐づく • = プラットフォームスレッド • JVM スレッド:OS スレッド= 1:1 の関係 Java のスレッド事情 – ざっくり歴史 OS スレッド JVM スレッド ・・・ JVM スレッド OS スレッド ・・・ OS スレッド JVM スレッド JVM スレッド 1 1 M 1
  12. Copyright © 2023, Oracle and/or its affiliates 13 • (初期の

    Solaris は厳密には違うが)1 つの Java スレッドは、1 つの OS スレッドの薄いラッパー • OS スレッドの生成は、CPU、メモリ、等のコストが高い • スレッド間の切り替え(= コンテキスト・スイッチ)もコストが高い • 利用可能なスレッドの数を制限している • 事前に一定数作成したスレッドを効率的に使いまわすための仕組み(= スレッドプール)を用いてきた Java でスレッドを扱う際の問題点(~Java 18)とその対応
  13. Copyright © 2023, Oracle and/or its affiliates 15 JEP(JDK Enhancement-Proposal)

    • JDK に対する重要な変更を設計および実装するための提案 Projects • 大きなインパクトのある機能については、JEP だけではなく OpenJDK のサブプロジェクトを発足して開発を行う • Project Amber, Loom, Panama, Valhalla, etc. 前提知識
  14. Copyright © 2023, Oracle and/or its affiliates 16 Project Loom

    • Java プラットフォーム上で動作し、使いやすくスループットの高い軽量並行処理と新しいプログラミングモデルを実現する ことを目的としたプロジェクト • 新しい構成要素を追加することで対応 • 仮想スレッド ← 今回のテーマ • 限定継続 • 末尾呼出しの削除 Project Loom
  15. Copyright © 2023, Oracle and/or its affiliates 17 • JVM

    上に仮想的なスレッド(Virtual Threads)を導入すること • Java でも 言語のスレッド:OS のスレッド = M(:N):N な関係を作れるようにする! • 他の言語でいうところの Fiber, Coroutine, SemiCoroutine, … • java.lang.Thread の API を使用する既存のコードに対して、最小限の変更で仮想スレッドを利用できるようにすること • 既存の JDK ツールを用いたトラブルシューティング、デバッグ、プロファイリングは容易に行えるように Virtual Threads – JEP 436 導入の目的 & ゴール OS スレッド ・・・ OS スレッド JVM スレッド JVM スレッド OS スレッド ・・・ OS スレッド JVM スレッド JVM スレッド JVM スレッド JVM スレッド ・・・ JVM スレッド JVM スレッド ・・・ 1 1 M N N ~ Java 18 までの Java のスレッドの仕組み JEP 425 で追加される仮想スレッドの仕組み
  16. Copyright © 2023, Oracle and/or its affiliates 18 使い方 •

    プレビュー版の API なので、コンパイルや実行時に --enable-preview のフラグが必要 • e.g. java --source 19 --enable-preview Main.java, jshell --enable-preview • 仮想スレッドの作り方 → 通常は、java.util.concurrent.Executors に新しく追加された API を用いる Virtual Threads – JEP 436 Virtual Threads の使い方 ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); // or ExecutorService executor = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory());
  17. Copyright © 2023, Oracle and/or its affiliates 19 {n ∈

    N | n ≤ 10,000} に対して、スリープ(1,000 ms)を挟みながら FizzBuzz 問題*を行う *FizzBuzz 問題: • n が 3 の倍数 → Fizz を出力 • n が 5 の倍数 → Buzz を出力 Demo2: Virtual Threads を実際に使ってみる vs. Platform Thread public static void main(String[] args) { long start = System.currentTimeMillis(); ThreadFactory virtualThreadFactory = Thread.ofVirtual().factory(); createThreadPerExecFizzBuzz(virtualThreadFactory); long end = System.currentTimeMillis(); logger.info("Execution time: " + Long.toString(end - start) + "[ms]"); } public static void createThreadPerExecFizzBuzz(ThreadFactory factory) { FizzBuzzTask task = new FizzBuzzTask(); try (ExecutorService exector = Executors.newThreadPerTaskExecutor(factory)) { IntStream.rangeClosed(1, 10_000).forEach(i -> { exector.submit(() -> { task.execWithSleep(i, 1_000); Utils.log(i + “: ” + task.exec(i)); // スレッド名と併せてログ出力 }); }); } } public static void main(String[] args) { long start = System.currentTimeMillis(); ThreadFactory platformThreadFactory = Thread.ofPlatform().factory(); createThreadPerExecFizzBuzz(platformThreadFactory); long end = System.currentTimeMillis(); logger.info("Execution time: " + Long.toString(end - start) + "[ms]"); } public static void createThreadPerExecFizzBuzz(ThreadFactory factory) { FizzBuzzTask task = new FizzBuzzTask(); try (ExecutorService exector = Executors.newThreadPerTaskExecutor(factory)) { IntStream.rangeClosed(1, 10_000).forEach(i -> { exector.submit(() -> { task.execWithSleep(i, 1_000); Utils.log(i + “: ” + task.exec(i)); // スレッド名と併せてログ出力 }); }); } } Virtual Thread Platform Thread
  18. Copyright © 2023, Oracle and/or its affiliates 20 • .oO

    そういえば、 java.util.concurrent.Executors には、固定長のスレッドプール(Single/FixedThreadPool)を 作る API や上限(Integer.MAX_VALUE)を設けたうえで、キャッシュしながら必要な数分のスレッドを作るスレッド プール(CachedThreadPool)が提供されているが、それらを使うと何か問題になるのだろうか…? • 仮想スレッドはプールせずに使うこと! • 生成コストが高価な OS のスレッドを必要以上に作成しないために、今まではスレッドプールを使っていた • そもそも仮想スレッドの生成は高価ではないので、プールする必要がない • プールする代わりに、スレッドの生成に制約を設けないようなモデル(= タスクの実行毎に仮想スレッドを作成するモ デル)を採用 • Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()) • Executors.newVirtualThreadTaskExecutor() • リソースへのアクセス数を制限したい場合は、java.util.concurrent.Semaphore を使用する Virtual Threads – JEP 436 仮想スレッドはプールせずに使用する 実現するための API が追加されています!
  19. Copyright © 2023, Oracle and/or its affiliates 21 • 従来

    JDK が提供してきたデバッグや監視用途のツールも同様に使用可能 • Debugger • 所謂、デバッガー • 仮想スレッドのステップ実行やコールスタックの表示、スタックフレーム内の変数の検査が可能 • JFR(JDK Flight Recorder) • 実行中の Java アプリケーションに関する診断およびプロファイリングのデータを低オーバーヘッドで収集するツール • アプリケーションコードからのイベント(オブジェクトの割り当て、I/O 操作、etc.)を正しく仮想スレッドに割り当て可能 • スレッドダンプ(プロセスの一部となっているすべてのスレッドの状態のスナップショット) • jcmd や jstack で取得可能 • スレッドの数が莫大に増えることを考慮し、従来のフラットな構造に加え、JSON 形式の出力をサポート Virtual Threads – JEP 436 仮想スレッドの監視 仮想スレッドは、JDK に実装されており、特定の OS スレッドに紐づけられていないため、 監視の際は OS レベルの監視ツールではなく、上記のようなツールを用いることが重要!
  20. Copyright © 2023, Oracle and/or its affiliates 22 1. Debugger

    • 仮想スレッドのステップ実行やコールスタックの表示、スタックフレーム内の変数の検査をしてみる 2. JFR, JDK Mission Control を用いてメトリクスを分析してみる 3. jcmd を用いてスレッドダンプを確認してみる Demo3: Virtual Threads の監視 Debugger/JFR(JDK Flight Recoder) + JDK Mission Control/スレッドダンプ public static void createThreadPerExecFizzBuzz(ThreadFactory factory) { FizzBuzzTask task = new FizzBuzzTask(); try (ExecutorService exector = Executors.newThreadPerTaskExecutor(factory)) { IntStream.rangeClosed(1, 10_000).forEach(i -> { exector.submit(() -> { task.execWithSleep(i, 1_000); Utils.log(i + “: ” + task.exec(i)); }); }); } } デバッグ実行 $ jcmd <PID> JFR.start duration=1m filename=oochacafe.jfr $ jcmd <PID> Thread.dump_to_file -format=json ochacafe.json JMC
  21. Copyright © 2023, Oracle and/or its affiliates 23 • スレッドを有効的に使うためには、スケジューリングが必要

    • プラットフォームスレッド(OS スレッドの薄いラッパー) • OS 内のスケジューラーによってスケジューリングされる • 仮想スレッド • JDK 独自のスケジューラー(work-stearing ForkJoinPool)によってスケジューリングされる • 仮想スレッドをプラットフォームスレッドに割り当てた後、OS 内のスケジューラーによってスケジューリングされる • 仮想スレッドは、ライフサイクル中で異なるキャリアにスケジューリングされる可能性がある Virtual Threads – JEP 436 仮想スレッドのスケジューリング プラットフォームスレッド 1 仮想スレッド タスク マウント プラットフォームスレッド 1 仮想スレッド タスク プラットフォームスレッド 1 仮想スレッド タスク アンマウント プラットフォームスレッド 2 仮想スレッド タスク マウント ブロック操作 完了準備 OK ブロック 操作
  22. Copyright © 2023, Oracle and/or its affiliates 24 • キャリアと仮想スレッドのスタックトレースは別もの

    • 仮想スレッドで投げられた例外は、キャリアのスタックフレームを含まない(※逆も同様) • スレッドダンプでは、仮想スレッドのスタックにキャリアのスタックフレームは表示されない(※逆も同様) • キャリアのスレッドローカル変数は、仮想スレッドでは使用できない • スレッドローカル変数の使用は慎重に検討する • → 膨大な数のスレッドで実行されるときのメモリのフットプリントを考慮すること Virtual Threads – JEP 436 仮想スレッドを使用する上での諸注意
  23. Copyright © 2023, Oracle and/or its affiliates 25 • 同時実行のタスク数が多い(数千以上)

    • 作業負荷が CPU バウンドではないこと • 長い時間計算を行うような場合には、効果がない • e.g. 巨大な配列をソートする、etc. • 高速化(低レイテンシー)ではなく、高スループットを実現するための技術 Virtual Threads – JEP 436 仮想スレッドが有効な場面
  24. Copyright © 2023, Oracle and/or its affiliates 26 • 計算処理の時間

    << 通信、DB アクセス等の I/O 処理にかかる時間(待ち時間含む) • I/O 処理の待ち時間に別の処理を行いたい… e.g. 一般的なサーバーサイドアプリケーション client trip-manager hotel-booking flight-booking POST /trip POST /hotel book() book() POST /flight 待ち 待ち 待ち 待ち 仮想スレッドが 有効的に使えるのでは?
  25. Copyright © 2023, Oracle and/or its affiliates 27 • Java

    でも OS のスレッド:言語のスレッド = M(:N):N な関係が作れるようになる • 2023 年 2 月現在は、プレビュー機能なので、使用する際は --enable-preview のフラグが必要 • 順調にいけば、次の LTS である Java 21 に導入か…? • 高速化(低レイテンシー)ではなく、高スループットを実現するための技術 • Java を使う開発者にとっては、慣れ親しんだ方法で仮想スレッドを使うことができる • スレッドの作成、JDK ツールを用いた監視、etc. • 仮想スレッドを使う場合、今までのスレッドに対する常識は注意が必要なものも… • スレッドプール、(場合によっては)スレッドローカル変数、etc. ここまでのまとめ
  26. Copyright © 2023, Oracle and/or its affiliates 29 Oracle がホストする

    OSS プロジェクト • GitHub でソースコードを公開:https://github.com/oracle/helidon • Helidon の商用サポートは WebLogic Server/Coherence/Verrazzano のサポート契約に含まれる マイクロサービスアプリケーションが必要とする機能を提供する Java ライブラリの集合体 • 単体の JVM として動作し、アプリケーションサーバ不要、容易なコンテナ化 • 必要なコンポーネントを追加して拡張することも可能 マイクロサービスの開発・運用を支援する機能を提供 • OpenMetrics(監視)、OpenTracing(追跡)、OpenAPI(API 公開) • 耐障害性/回復性: ヘルスチェック、サーキット・ブレーカ 2つのプログラミングモデルを提供 • Helidon MP:宣言的記法(Java/Jakarta EE 開発者フレンドリー) • Helidon SE:関数的型記法 Project Helidon クラウドネイティブなマイクロサービスを開発するための Java アプリケーションフレームワーク
  27. Copyright © 2023, Oracle and/or its affiliates 30 Helidon SE

    と MP • Eclipse MicroProfile 準拠 • 軽量フットプリント • 宣言型 • Java EE サブセット + マイクロサービス関連機能 • マイクロ・フレームワーク • 超軽量フットプリント • 関数型 • Reactive, non-blocking Helidon MP Helidon SE ≒ + Eclipse MicroProfile + 拡張機能 フットプリント重視 Polyglot 言語開発者向け 機能性・互換性重視 Java/Jakarta EE 開発経験者向け
  28. Copyright © 2023, Oracle and/or its affiliates 31 • Helidon

    はライブラリをパッケージングした Java SE アプリケーションとして実行される(= Executable JAR) • Helidon アプリケーションは個々の JVM 上で実行 • 複数のパッケージング・オプション、Docker にも対応 – ビルド・ツールを使って簡単に作成 多彩なパッケージング方法を提供 Executable JAR Jlink ランタイム・イメージ GraalVM ネイティブ・イメージ Container Application Java Runtime Linux Container Custom Java Runtime Linux Container Native Application Linux Application
  29. Copyright © 2023, Oracle and/or its affiliates 32 2018 2020

    2022 2023 Helidon の生い立ちとこれから Helidon 0.x – 1.x Helidon 2.x Helidon 3.x Helidon 4.x • Java 8 • MicroProfile 3.2 • Java 11 • MicroProfile 3.3 • Java 17 • MicroProfile 5.0 • Java 19 (Java 21) • MicroProfile Next
  30. Copyright © 2023, Oracle and/or its affiliates 33 アプローチ 1

    – Reactive Programming • ノンブロッキング • イベントループの使用 • 少数のスレッドですべてのリクエストを処理する アプローチ 2 – Virtual Threads // NEW!! • ブロッキング • 安価にスレッド(仮想スレッド)を作成することで高いスループットを実現 高スループットを実現するための “Helidon” のアプローチ Reactive プログラミングの詳細はこちらから
  31. Copyright © 2023, Oracle and/or its affiliates 34 • https://helidon.io/nima

    • νήμα(nima)は、ギリシャ語で「糸、毛糸、編み糸」という意味 • 仮想スレッド(Virtual Threads)ベースの Java のマイクロサービス・フレームワーク • Helidon のエコシステムにおける Netty* の置き換えがゴール • アプリケーションをブロッキングでシンプルに実装可能 • ブロッキングな実装でも高いスループットが実現可能 • 2023 年 2 月現在:テクノロジープレビュー版を Helidon 4.0.0-ALPHA4で公開中 • https://github.com/helidon-io/helidon/tree/4.0.0-ALPHA4 • 提供機能: • HTTP/1, HTTP/2, gRPC, Metrics, Health Checks, Tracing, Fault Tolerance, Testing Integration *: イベントドリブンな非同期通信を行うアプリケーションを開発するためのフレームワーク Helidon Níma Virtual Threads によるブロッキング・スレッドモデルの採用
  32. Copyright © 2023, Oracle and/or its affiliates 35 Níma vs.

    other servers 0 100000 200000 300000 400000 500000 600000 700000 800000 900000 JSON HTTP/2 TLS Requests/second Netty Níma Helidon MP (Níma) Dropwizard https://medium.com/helidon/helidon-n%C3%ADma-helidon-on-virtual-threads-130bb2ea2088
  33. Copyright © 2023, Oracle and/or its affiliates 36 Helidon MP

    on Níma Virtual Threads ベースの Helidon によるスループットの向上 0 50000 100000 150000 200000 250000 300000 350000 400000 JSON HTTP/2 TLS Requests/second Platform threads Virtual threads
  34. Copyright © 2023, Oracle and/or its affiliates 37 対象のアプリケーション: •

    Helidon MP on Níma • 4.0.0-ALPHA4 • Helidon MP/SE • 3.1.0 • Spring Boot • 3.0.2 Demo4: Helidon Níma のスループットを体験してみる シンプルな文字列を返却するアプリケーションに負荷をかけてスループットを体験する --vus 50 --duration 15s /cowsay/say
  35. Copyright © 2023, Oracle and/or its affiliates 38 仮想スレッド(Virtual Threads)

    • Java でも OS のスレッド:言語のスレッド = M(:N):N な関係を作れるように • 高速化(低レイテンシー)ではなく、高スループットを実現するための技術 • 慣れ親しんだ方法で仮想スレッドを使うことができるが、一部使い方には注意が必要 • スレッドプール、(場合によっては)スレッドローカル変数、etc. Helidon Níma • 仮想スレッド(Virtual Threads)ベースの Java のマイクロサービス・フレームワーク • 仮想スレッドを前提とした HTTP、gRPC、WebSocket のブロッキング・モデルの実装を提供 • ブロッキング・モデル実装でも高いスループットを実現 まとめ
  36. Copyright © 2023, Oracle and/or its affiliates 39 • JEP

    436: Virtual Threads (Second Preview) • https://openjdk.org/jeps/436 • Loom – Fibers, Continuations and Tail-Calls for the JVM • https://openjdk.org/projects/loom/ • JavaのProject Loomと仮想スレッドの内部 • https://blogs.oracle.com/oracle4engineer/post/going-inside-javas-project-loom-and-virtual- threads-ja • Helidon Níma • https://helidon.io/nima • Helidon Níma – Helidon on Virtual Thread • https://medium.com/helidon/helidon-n%C3%ADma-helidon-on-virtual-threads-130bb2ea2088 • oracle-japan/ochacafe-concurrent-programming • https://github.com/oracle-japan/ochacafe-concurrent-programming 参考情報