スレッド プールのサイズとスレッド数を心配するのはやめましょう。決まった計算式はありません | JD Cloud Technology Team

多くの人は、スレッド数の設定に関する理論を見たことがあるかもしれません。

  • CPU 集中型プログラム - コア数 + 1

  • I/O 集中型プログラム - コア数 * 2

いやいや、本当にこの理論に従ってスレッド数を計画する人がいるでしょうか?

スレッド数と CPU 使用率の小規模なテスト

オペレーティング システムやコンピューターの原理についてはさておき、基本的な理論について話しましょう (厳密かどうかは気にする必要はありません。理解しやすいように)。CPU コアは単位時間あたり 1 つのスレッドの命令しか実行できません

したがって、理論的には、1 つのスレッドが命令を実行し続けるだけで、1 つのコアを完全に利用できるようになります。

検証するために無限ループで実行する例を書いてみましょう。

テスト環境: AMD Ryzen 5 3600、6 - コア、12 - スレッド

public class CPUUtilizationTest {
	public static void main(String[] args) {
		//死循环,什么都不做
		while (true){
		}
	}
}

この例を実行した後、現在の CPU 使用率を見てみましょう。
画像.png
写真からわかるように、3 番コアの使用率はすでにいっぱいです。

上記の理論に基づいて、もう少しスレッドを開いてみる必要がありますか?

public class CPUUtilizationTest {
	public static void main(String[] args) {

		for (int j = 0; j < 6; j++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (true){
					}
				}
			}).start();
		}
	}
}

この時点の CPU 使用率を見ると、1/2/5/7/9/11 のいくつかのコアの使用率がすでにいっぱいになっています。
画像.png

では、12 スレッドを開いた場合、すべてのコアが完全に利用されるでしょうか? 答えは「はい」でなければなりません
画像.png

この時点で上記の例のスレッド数を 24 スレッドに増やし続けると、結果はどうなりますか?
画像.png

上の図からわかるように、CPU 使用率は前のステップと同じで、すべてのコアで 100% のままですが、この時点で負荷は 11.x から 22.x に増加しています (負荷平均の説明のため)これは、現時点で CPU がビジー状態であり、スレッドのタスクを時間内に実行できないことを示しています

最近の CPU は基本的にマルチコアです。たとえば、今回テストした AMD 3600 は 6 コアと 12 スレッド (ハイパースレッディング) を備えており、単純に 12 コア CPU と考えることができます。そうすれば、私の CPU は、お互いに邪魔をすることなく、同時に 12 のことを実行できます。

実行するスレッドの数がコアの数より大きい場合は、オペレーティング システムによってスケジュールする必要があります。オペレーティング システムは、CPU タイム スライス リソースを各スレッドに割り当て、継続的に切り替えて「並列」実行の効果を実現します。

しかし、これは本当に速いのでしょうか? 上記の例からわかるように、1 つのスレッドは1 つのコアの使用率を最大限に活用できます各スレッドが非常に「高圧的」で、CPU にアイドル時間を与えずに命令を実行し続け、同時に実行するスレッドの数が CPU のコアの数よりも多い場合、オペレーティング システムがスレッドの実行を切り替える原因になります。すべてのスレッドを確実に実行できるようにするために、より頻繁に実行されます。

ただし、切り替えにはコストがかかり、各切り替えにはレジスタデータの更新やメモリページテーブルの更新などの操作が伴いますスイッチのコストは I/O 操作に比べて無視できますが、スレッドが多すぎたり、スレッドのスイッチが頻繁に発生したり、単位時間あたりのスイッチ時間がプログラムの実行時間を超えたりすると、CPU の過剰使用につながります。プログラムを実行する代わりにコンテキストの切り替えにリソースが無駄になるため、利得の方が大きくなります。

無限ループで実行する上記の例は少し極端すぎます。通常の状況ではそのようなプログラムが存在する可能性は低いです。

ほとんどのプログラムでは、実行中にいくつかの I/O 操作が行われます。これには、ファイルの読み取りと書き込み、ネットワーク上でのメッセージの送受信などが含まれます。これらの I/O 操作は、実行中にフィードバックを待つ必要があります。たとえば、ネットワーク上で読み書きを行う場合、メッセージの送受信を待つ必要がありますが、この待機処理中はスレッドが待機状態となり、CPU は動作しません。このとき、オペレーティング システムは CPU が他のスレッドからの命令を実行するようにスケジュールするため、CPU のアイドル期間が最大限に活用され、CPU 使用率が向上します。

上記の例では、プログラムはループを続けて何もせず、CPU は命令を実行し続ける必要があるため、自由時間がほとんどなくなります。I/O 操作が挿入され、I/O 操作中に CPU がアイドル状態になった場合はどうなりますか? CPU 使用率はどうなりますか? まず、単一スレッドでの結果を見てみましょう。

public class CPUUtilizationTest {
	public static void main(String[] args) throws InterruptedException {

		for (int n = 0; n < 1; n++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (true){
                        //每次空循环 1亿 次后,sleep 50ms,模拟 I/O等待、切换
						for (int i = 0; i < 100_000_000l; i++) { 
						}
						try {
							Thread.sleep(50);
						}
						catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}).start();
		}
	}
}

画像.png

なんと、コア9番だけの稼働率は50%しかなく、スリープなしの前回の100%と比べるとすでに半分以下になってしまいました。次に、スレッド数を 12 に調整して、次を確認します。
画像.png

シングル コアの使用率は約 60 で、先ほどのシングル スレッドの結果とあまり変わりません。CPU 使用率はまだ完全に達していません。次に、スレッドの数を 18 に増やします。
画像.png

現時点では、単一コアの使用率は 100% に近くなります。CPU リソースを占有しない I/O やその他の操作がスレッド内にある場合、オペレーティング システムはより多くのスレッドを同時に実行するように CPU をスケジュールできることがわかります。

次に、I/O イベントの頻度を増やし、ループ数を半分の 50_000_000 (同じ 18 スレッド) に減らします。


画像.png

現時点では、各コアの使用率は約 70% にすぎません。

スレッド数と CPU 使用率の簡単な概要

上記の例は単なる補助的なものですが、スレッド数/プログラムの動作/CPU ステータスの関係をよりよく理解するために、簡単にまとめてみましょう。

  1. エクストリーム スレッド (常に「コンピューティング」操作を実行する場合) は、単一コアの使用率を最大限に活用できますが、マルチコア CPU は、コアの数と同じ最大数の「エクストリーム」スレッドのみを同時に実行できます。

  2. 各スレッドが「極端」で、同時に実行するスレッドの数がコアの数を超えると、不要な切り替えが発生し、負荷が高くなりすぎて、実行が遅くなるだけです。

  3. I/O などの一時停止操作中、CPU はアイドル状態になり、オペレーティング システムは CPU が他のスレッドを実行するようにスケジュールするため、CPU 使用率が向上し、より多くのスレッドを同時に実行できます。

  4. I/O イベントの頻度が高いほど、または待機/一時停止時間が長いほど、CPU のアイドル時間が長くなり、使用率が低くなるほど、オペレーティング システムはより多くのスレッドを実行するように CPU をスケジュールできます。

スレッド数計画の公式

これまでの伏線は理解を助けるためのもので、次に本書の定義を見てみましょう。「Java Concurrent Programming in Practice」では、スレッド数を計算するための公式を紹介しています。

クリーンショット 2023-09-07 12.41.41@2x.png

プログラムを目標の CPU 使用率まで実行する場合、必要なスレッド数の計算式は次のとおりです。

クリーンショット 2023-09-07 12.42.02@2x.png

式は非常に明確なので、上の例で試してみましょう。

目標使用率が 90% (マルチコア 90) であると予想される場合、必要なスレッド数は次のようになります。

コア数 12 * 使用率 0.9 * (1 + 50 (スリープ時間)/50 (サイクル 50_000_000 時間かかる)) ≈ 22

次に、スレッド数を 22 に調整し、結果を確認します。
画像.png

CPU 使用率は現在約 80+ で、予想に近い値ですが、過剰なスレッド数、コンテキスト切り替えのオーバーヘッド、および厳密なテスト ケースの欠如により、実際の使用率はこれより低くなるのが通常です。

式を変更すると、スレッド数によって CPU 使用率を計算することもできます。

クリーンショット 2023-09-07 12.41.11@2x.png

スレッド数 22 / (コア数 12 * (1 + 50 (スリープ時間) / 50 (サイクル 50_000_000 時間かかる))) ≈ 0.9

式は良いのですが、実際のプログラムでは、プログラムが複雑であり、単に「計算」するだけではないため、正確な待ち時間と計算時間を求めるのは一般に困難です1 つのコード内にはメモリの読み書き、計算、I/O などの複合操作が多数含まれており、これら 2 つの指標を正確に取得することは困難であるため、式だけでスレッド数を計算するのは理想的すぎます。

実際のプログラムのスレッド数

では、実際のプログラムや一部の Java ビジネス システムでは、どのくらいのスレッド数 (スレッド プール サイズ) を計画するのが適切でしょうか?

結論を先に話します: 決まった答えはありません。まず、CPU 使用率がどうなるか、負荷はどれくらいか、GC 周波数はどれくらいか、その他の指標などの期待値を設定し、その後継続的に調整します。テストを通じて適切な数のスレッドを取得します。

たとえば、通常の SpringBoot ベースのビジネス システムの場合、デフォルトの Tomcat コンテナ+HikariCP 接続プール + G1 リサイクラー (この時点でプロジェクトがビジネスを実行するためのビジネス シナリオにマルチスレッド (またはスレッド プール) も必要な場合)非同期/並列で処理します。

このとき、上記の計算式に従ってスレッド数を計画すると誤差が非常に大きくなります。現時点では、このホストにはすでに多くのスレッドが実行されているため、Tomcat には独自のスレッド プールがあり、HikariCP にも独自のバックグラウンド スレッドがあり、JVM にもいくつかのコンパイル済みスレッドがあり、さらに G1 にも独自のバックグラウンド スレッドがあります。これらのスレッドは現在のプロセスおよび現在のホストでも実行され、CPU リソースも占有します。

したがって、環境の影響により、式のみに頼ってスレッド数を正確に計画することは困難であり、テストによって検証する必要があります。

プロセスは一般に次のとおりです。

  1. 現在のホスト上の他のプロセスからの干渉があるかどうかを分析します。

  2. 現在の JVM プロセス上に他の実行中のスレッド、または実行中の可能性のあるスレッドがあるかどうかを分析します。

  3. 目標を設定します

    1. 目標の CPU 使用率 - CPU の使用率はどの程度まで許容できますか?

    2. 目標の GC 頻度/一時停止時間 - マルチスレッド実行後、GC 頻度は増加しますが、許容できる最大頻度と各一時停止時間はどれくらいですか?

    3. 実行効率 - たとえば、バッチ処理中、時間内に処理を完了するには、単位時間あたり何個のスレッドを開く必要がありますか?

    4. ……

  4. リンクのキー ポイントを整理して、スタック ポイントがあるかどうかを確認します。スレッドが多すぎると、リンク上の一部のノードのリソースが限られているため、多数のスレッドがリソースを待機する可能性があります (3 つのスレッドなど)。パーティ インターフェイスの電流制限、接続プールの数の制限、中間パーツへの圧力が高すぎるためサポートできないなど)

  5. テストするスレッドの数を継続的に増減させ、最も高い要件に従ってテストし、最終的に「要件を満たす」スレッドの数を取得します**

そしてそしてそして!シナリオごとにスレッド番号の概念も異なります。

  1. Tomcat の maxThreads は、ブロッキング I/O と非ブロッキング I/O で異なります。

  2. Dubbo の接続はデフォルトで 1 つだけです。I/O スレッド (プール) とビジネス スレッド (プール) も区別されます。I/O スレッドは通常ボトルネックではないため、多すぎる必要はありませんが、ビジネススレッドは簡単にボトルネックと呼ばれる可能性があります。

  3. Redis も 6.0 以降はマルチスレッド化されていますが、I/O のみがマルチスレッド化されており、「ビジネス」処理は依然としてシングルスレッド化されています。

したがって、設定するスレッドの数について心配する必要はありません。標準的な答えはなく、シナリオと目標を組み合わせ、テストを通じて最適なスレッド数を見つける必要があります。

学生の中には、「システムに負荷はかかっていません。そのような適切な数のスレッドは必要ありません。これは、システムの他の機能に影響を与えない単純な非同期シナリオにすぎません。」と疑問を持つ人もいるかもしれません。

これは正常なことであり、社内ビジネス システムの多くは、安定していて使いやすく、ニーズを満たしていれば、それほどパフォーマンスを必要としません。次に、私が推奨するスレッド数は次のとおりです。CPU コアの数

付録

JavaはCPUコアの数を取得します

Runtime.getRuntime().availableProcessors()//获取逻辑核心数,如6核心12线程,那么返回的是12

Linux CPUコア数を取得する

# 总核数 = 物理CPU个数 X 每颗物理CPU的核数 
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数

# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq

# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l

私の記事が役に立った場合は、いいね/集め/フォローして励まし、サポートしてください❤❤❤❤❤❤

 

著者:JD Insurance Jiang Xin

出典:JD Cloud Developer Community 転載の際は出典を明記してください

JetBrains が Rust IDE をリリース: RustRover Java 21 / JDK 21 (LTS) GA 中国には非常に多くの Java 開発者がいることから、エコロジーレベルのアプリケーション開発フレームワーク .NET 8 が誕生するはずであり、パフォーマンスは大幅に向上しており、 をはるかに上回っています。 NET 7. PostgreSQL 16 は、Rust チームの元メンバーによってリリースされました。大変遺憾ながら名前をキャンセルしていただきました。 昨日、フロントエンドの Nue JS の削除を完了しました。作者は、新しい Web エコシステムを作成すると言っています。 NetEase Fuxi、「バグにより人事部から脅迫された」従業員の死亡に対応 任正非氏:私たちは第4次産業革命を迎えようとしている、Appleはファーウェイの師であるVercelの新製品「v0」:UIインターフェースコードをベースに生成文章
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10112038