着信要求ごとに個別のスレッドを使用する REST サービス用の古い Java コードがあります。つまり、メイン ループは socket.accept() でループし、ソケットを Runnable に渡します。Runnable は独自のバックグラウンド スレッドを起動し、それ自体で run を呼び出します。これはしばらくの間見事にうまく機能していましたが、最近、要求を処理するための受け入れの遅れが高負荷下で受け入れられなくなることに気付きました。見事にうまく言えば、CPU をあまり使用せずに 1 秒あたり 100 ~ 200 のリクエストを処理していたことを意味します。パフォーマンスが低下したのは、他のデーモンも負荷を追加していたときだけで、負荷が 5 を超えたのは 1 回だけでした。マシンが他のプロセスの組み合わせから高い負荷 (5-8) にさらされていた場合、受け入れから処理までの時間が途方もなく長くなりました ( 500 ミリ秒から 3000 ミリ秒)、実際の処理は 10 ミリ秒未満のままでした。
.NET のスレッドプールに慣れていたので、スレッドの作成が原因であると想定し、Java で同じパターンを適用すると考えました。今私の Runnable は ThreadPool.Executor で実行されます (そしてプールは ArrayBlockingQueue を使用します)。繰り返しになりますが、マシンの負荷が高くならない限り、ほとんどのシナリオでうまく機能します。その後、ランナブルの作成から run() が呼び出されるまでの時間は、ほぼ同じばかげたタイミングを示します。さらに悪いことに、スレッドプール ロジックを導入すると、システム負荷がほぼ 2 倍 (10-16) になりました。そのため、負荷が 2 倍になると同じ遅延の問題が発生します。
私の疑いでは、キューのロック競合は、ロックのない以前の新しいスレッドの起動コストよりも悪いということです。新しいスレッドとスレッドプールの経験を共有できる人はいますか? そして、私の疑いが正しければ、ロック競合なしでスレッドプールを処理するための代替アプローチを誰かが持っていますか?
スレッド化がどれだけ役立つかわからず、IO が問題にならないように見えるので、システム全体をシングルスレッド化したくなるかもしれませんが、長寿命のリクエストがいくつかあります。すべてをブロックします。
ありがとう、アルネ
更新: 切り替えてExecutors.newFixedThreadPool(100);
、同じ処理能力を維持しながら、負荷はすぐにほぼ 2 倍になり、12 時間実行すると、負荷が常に 2 倍にとどまることがわかりました。私の場合、リクエストごとの新しいスレッドの方が安いと思います。