非同期モードのワーカー スレッド

目次

意味

飢え

作成するのに適切なスレッド プールの数はいくつですか

CPU 負荷の高い操作

I/O 集中型の操作


意味

制限されたワーカー スレッド (ワーカー スレッド) が交代で無制限のタスクを非同期に処理できるようにします。これは分業モードとしても分類でき、典型的な実装はスレッド プールであり、クラシック デザイン モードのフライウェイト モードも反映されます。

たとえば、Haidilao のウェイター (スレッド) は各ゲストの注文 (タスク) を順番に処理しますが、各ゲストに専用のウェイターが割り当てられている場合、コストが高くなりすぎます (別のマルチスレッド設計パターン: Thread-Per-Message と比較して)

異なるタスクタイプは異なるスレッドプールを使用する必要があることに注意してください。これにより、枯渇を回避し、効率を向上させることができます。

たとえば、レストランの従業員がゲストを出迎え (タスク タイプ A)、裏のキッチンに行って調理する必要がある場合 (タスク タイプ B) は明らかに効率が悪いため、従業員をウェイター (スレッド プール A) とウェイター (スレッド プール A) に分けるのはより困難です。シェフ (スレッド プール B) 当然のことながら、より詳細な分業を考えることもできます。

飢え

固定サイズのスレッド プールが不足します。

2 つのワーカーは同じスレッド プール内の 2 つのスレッドです

彼らがしなければならないことは、ゲストに注文を出し、キッチンで調理するという 2 段階の仕事です。

  • ゲストは食べ物を注文します。最初に食べ物を注文し、食べ物が準備できるのを待って、食べ物を提供する必要があります。この間、注文を処理する従業員は待機する必要があります。
  • 裏のキッチンで料理:何も言うことはありません、ただやってください

たとえば、従業員 A が料理の注文を担当し、従業員 B が料理を準備して提供するのを待つ必要があります。

しかし、今度は 2 人の客が同時に来ます。この時点では、従業員 A と従業員 B の両方が注文を処理する予定です。この時点では、誰も料理をしていなくて、お腹が空いています。

public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(()->{
            System.out.println("点餐中...");
            Future<String> future = executorService.submit(() -> {
                return "宫保鸡丁1";
            });
            try {
                String s = future.get();
                System.out.println("上菜"+s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

        executorService.submit(()->{
            System.out.println("点餐中...");
            Future<String> future = executorService.submit(() -> {
                return "宫保鸡丁2";
            });
            try {
                String s = future.get();
                System.out.println("上菜"+s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}

操作の結果は次のようになります。

注文中...
注文中...
カンパオチキン 2 を提供
カンパオチキン 1 を提供

 コアスレッドを3に変更する場合、つまりコードを変更する場合

ExecutorService executorService = Executors.newFixedThreadPool(3);

 その後、実行結果は次のようになります。

注文中...
注文中...
カンパオチキン 2 を提供
カンパオチキン 1 を提供

解決策はスレッド プールのサイズを増やすことですが、根本的な解決策ではありません。前述したように、タスクの種類が異なれば、使用するスレッド プールも異なります。たとえば、次のとおりです。

public class Test {
    public static void main(String[] args) {
        ExecutorService orderExecutorService = Executors.newFixedThreadPool(1);
        ExecutorService cookExecutorService = Executors.newFixedThreadPool(1);
        orderExecutorService.submit(()->{
            System.out.println("点餐中...");
            Future<String> future = cookExecutorService.submit(() -> {
                return "宫保鸡丁1";
            });
            try {
                String s = future.get();
                System.out.println("上菜"+s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

        orderExecutorService.submit(()->{
            System.out.println("点餐中...");
            Future<String> future = cookExecutorService.submit(() -> {
                return "宫保鸡丁2";
            });
            try {
                String s = future.get();
                System.out.println("上菜"+s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}

操作の結果は次のようになります。

 注文...
カンパオチキン 1 の提供
注文...
カンパオチキン 2 の提供

作成するのに適切なスレッド プールの数はいくつですか

スレッド プールのサイズは、特定のアプリケーション シナリオとシステム要件に従って決定する必要があります。参考として次のような提案があります。

  1. システム リソースを考慮する: スレッド プールのサイズは、システムで利用可能なリソースと一致する必要があります。スレッド プールのサイズが大きすぎると、システム メモリと CPU リソースが過剰に消費され、システムのパフォーマンスが低下します。スレッド プールのサイズが小さすぎると、システム リソースが十分に活用されず、タスクが発生する可能性があります。実行のためにキューに入れられます。

  2. タスクの種類を考慮する: タスクの種類が異なれば、スレッド プールに対する要求も異なります。タスクが CPU 集中型 (計算集中型) である場合、つまりタスクが実行中に主に CPU リソースを消費する場合、スレッド プールのサイズは CPU コアの数と同じかそれよりわずかに大きく設定できます。 IO 集約型 (入出力集約型)、つまりタスクが実行中に主に IO 操作 (ネットワーク リクエスト、ファイルの読み取りと書き込みなど) を消費する場合、通常はスレッド プールのサイズを設定できます。システムの IO 機能を最大限に活用するには、より大きくなります。

  3. タスクの応答時間を考慮する: タスクの応答時間に対する要件が高い場合、つまりユーザー要求に迅速に応答する必要がある場合は、スレッド プールのサイズを適切に増やして同時実行性と応答速度を向上させることができます。

  4. タスク キューイング戦略を考慮する: スレッド プールのサイズでは、タスク キューイング戦略も考慮する必要があります。スレッド プールが有界キューをタスク バッファとして使用する場合、タスクの数が多すぎる場合、キュー容量を超えるタスクは拒否されます。非有界キューをタスク バッファとして使用する場合は、スレッド プールのサイズを設定できます。より多くのタスクを実行のためにキューに入れることができるようにするには、この値を大きくします。

つまり、スレッド プールのサイズを決定するには、システム リソース、タスクの種類、応答時間、タスクのキューイング戦略などの要素を総合的に考慮し、実際のパフォーマンス テストとチューニングを実施する必要があります。実際の状況に応じてスレッド プールのサイズを常に調整し、最高のパフォーマンスとリソース使用率を実現します。

CPU 負荷の高い操作

通常、CPU コアの数 + 1 を使用すると、最適な CPU 使用率を実現できます。+1 は、ページ欠落障害 (オペレーティング システム) またはその他の理由でスレッドが中断された場合に、追加のスレッドを補充できるようにするためのものです。 CPU クロック サイクルが無駄にならないようにするため

I/O 集中型の操作

CPU は常にビジー状態にあるわけではありません。たとえば、ビジネス計算を実行するときはこの時点で CPU リソースが使用されますが、データベース操作を含む I/O 操作やリモート RPC 呼び出しを実行するときは、CPU はアイドル状態になります。 time Down の場合は、マルチスレッドを使用して使用率を向上させることができます。

実験式は次のとおりです

スレッド数 = コア数 * 予想される CPU 使用率 * 合計時間 (CPU 計算時間 + 待機時間) / CPU 計算時間

たとえば、4 コア CPU の計算時間が 50%、その他の待ち時間が 50%、予​​想される CPU が 100% 使用されている場合、次の式を適用します。

4 * 100% * 100% / 50% = 8

例えば、4コアCPUの計算時間は10%、その他の待ち時間は90%で、CPUが100%利用されると予想し、計算式を適用します。

4 * 100% * 100% / 10% = 40

おすすめ

転載: blog.csdn.net/m0_62436868/article/details/131342610