9

私はマルチスレッドプログラミングにかなり慣れていないので、次のアイデアを実装するための最良の方法についての洞察を得たいと思っていました。

今のところ私のコードはこのように機能します 現在の機能

単一スレッドであるため、各データを処理してデータベースに書き込むのにかかる時間で、新しいデータが入ってきてキューに入れられ、処理速度が大幅に低下します。4CPUサーバーで実行していますが、現在のセットアップでは1しか使用していません。

真ん中の部分で行われた作業を残りの3つのCPUに分けたいと思います。これをどのように行うのが最善でしょうか?新しいデータごとに新しいスレッドを作成できると思いましたが、私たちは1日のうちに数十万の新しいスレッドについて話し合っています。私が読んだところによると、それに関連するオーバーヘッドは非常に大きくなります。メモリは私にとって懸念事項なので、これらすべてのスレッドの作成がメモリを消費しすぎると、問題が発生します。新しいスレッドがビジーでないCPUを使用するというのは本当ですか、それとも同じJVMであるため、同じスレッドを使用するのでしょうか。

新しいデータごとの処理とDB書き込みは、その場合、数秒以上かかることはありません。

スレッドプールについても読んでいましたが、その考えは少し混乱していて、良い例を見つけることができません。

こんなことを考えていた アイディア

リーズナブルなデザインを考え出す際に、マルチスレッドの初心者を助けてください!前もって感謝します :-)

4

2 に答える 2

6

より重要な点は、並行して動作しているスレッドの数です(したがって、マシンを強制終了する可能性があります)。スレッドオブジェクトを次々に作成する場合は、より効率的に作成できますが、一般に、そのコストは(おそらく)ごくわずかです(Michalが指摘したように)。それはさておき(そしてマルチスレッドについて学びたいと仮定すると)、あなたのデザインはすでに十分に合理的に聞こえます。java.util.concurrentそれを達成するためのツールに関して、何があなたに提供できるかを見てみましょう。

  • ExecutorService:最良の選択でしょう。ワーカースレッドの固定スレッドプールを作成し、n着信スレッドごとにRunnable、処理を実行してすべてのデータをデータベースに保存するスレッドを投稿します。

    public class DataProcessor {
        final ExecutorService workerThreadPool = Executors.newFixedThreadPool(5);
    
        public void onNewDataFromTheOutsideWorld(Data d) { 
           workerThreadPool.execute(new ProcessingAndStoreToDBRunnable(d));
        }
    
        public void onShutdown() { 
           workerThreadPool.shutdown();
        }
    }
    

    ExecutorService、固定数のワーカーのみが実際に並行して実行されていることを確認します。

  • 独自のキューイングメカニズム:優先度の異なるジョブに関しては、独自の作業メカニズムを実装することをお勧めします。これははるかに複雑であり、ExecutorService可能であれば解決策に固執する必要があることに注意してください。

    基本的な考え方は、BlockingQueueデータが追加されるn場所を用意し、キューから読み取ったワーカースレッドをジョブで開始することです。秘訣は、ジョブがない場合(したがって、スレッドをスリープ状態にする)にキューがブロックされ、ジョブが複数ある場合n、処理スレッドが使用可能になるまで、ジョブがキューに格納されることです。

    public class DataProcessor {
        final BlockingQueue<Data> queue = new BlockingQueue<Data>();
    
        public void onInit() {
           for (int i = 0; i < n; i++) 
               new Thread(new WorkerRunnable(queue)).start();
        }
    
        public void onNewDataFromTheOutsideWorld(Data d) { 
           queue.add(d);
        }
    }
    
    public class WorkerRunnable implements Runnable {
        public void run() { 
           while (true) {
               Data d = queue.take();
               processData(d);
           }
        }
     }
    

    私が言ったように、私はまだ次のような問題に触れていないので、これを実現するのははるかに複雑です

    • ワーカースレッドを停止する
    • 必ず例外を処理してから、処理に戻ってください

これらは、マルチスレッド環境における基本的な(しかし非常に強力な)ツールです。より高度なツールが必要な場合は、Guavaライブラリをチェックアウトしてください。たとえば、ListenableFuture(ワーカースレッドの結果が必要な場合に使用する必要がある)というすばらしい概念があります。

そうすれば、かなり基本的な設計になり、コメントですでに指摘したように、そこからより高度な処理ステップを追加できます。また、それはかなり広い質問に変わることが指摘されました;)

于 2012-12-11T21:19:15.507 に答える
0

まず、このアプリケーションでのパフォーマンスの重要性と、処理する必要のあるトラフィックの種類を検討する必要があります。各リクエストに0.1ミリ秒のレイテンシーを追加することをあまり気にしない場合(各リクエストに数秒かかると言ってもそうは思わない)、新しいスレッドを作成することは目立ったコストにはなりません。スレッドは作業が終了した後に寿命を終える必要があることに注意してください。そのため、同時に数十万のスレッドが発生することはありません。スレッドは時間の経過とともに開始および終了します。1日あたり「数十万」のリクエストを受け取った場合、これは1秒あたり数回のリクエストにすぎません(均等に分割されていると仮定)。そのようなパラメータで、アクティブなリクエストの平均数は数十のオーダーになります(1秒あたり約10 x 1リクエストあたり数秒〜=数十のリクエストがいつでも有効です)。これはマシン上のコアの数を超えていますが、問題なく処理する必要があります。これらのスレッドがDBと通信する場合、ほとんどの時間を通信リンクの待機に費やします。リクエストごとに個別のスレッドを用意することは、一般的に最適な設計ではないかもしれませんが、FuturesとExecutorsについて学ぶような派手なものよりも、おそらく実装が簡単です。

したがって、両方のソリューションにはメリットがあります-より良い設計とおそらくより良いリソース使用の未来(これはスケジュールの程度に依存する可能性があります)とリクエストごとのスレッドで何かを高速に動作させる(そして内部で何が起こっているのかを簡単に理解できる)システム)。現在、並行性のみを学習している場合は、システムが舞台裏で何をする必要があるかを理解するために、最初はあまりエレガントではない方法で行うことをお勧めします。次に、その「手動」スケジューリングアプローチに精通している場合は、より高いレベルの抽象化に進み、Futures et alを学び、コードをリファクタリングできます。その2番目のバージョンは、Futuresからすぐに始めた場合に記述できるコードよりも、おそらくはるかに優れています。

于 2012-12-11T21:14:55.947 に答える