20

大きなファイル (ファイル サイズは 30 MB から 1 GB の範囲) に 10 スレッドでアクセスし、ファイル内の各行を処理して、10 スレッドで別のファイルに書き込みたいと考えています。1 つのスレッドのみを使用して IO にアクセスすると、他のスレッドがブロックされます。この処理には、ファイル システムから 1 行のコードを読み取るのとほぼ同じ時間がかかります。もう 1 つの制約があります。出力ファイルのデータは、入力ファイルのデータと同じ順序でなければなりません。

このシステムの設計について、あなたの考えを知りたいです。ファイルへの同時アクセスをサポートする既存の API はありますか?

また、同じファイルに書き込むと、デッドロックが発生する可能性があります。

時間の制約が気になる場合は、これを達成する方法を提案してください。

4

10 に答える 10

15

3 つのスレッドから始めます。

  1. データを読み取り、それを「行」に分割し、制限付きブロッキング キュー (Q1) に入れるリーダー スレッド、
  2. Q1 から読み取り、処理を実行し、それらを 2 番目の制限付きブロッキング キュー (Q2) に入れる処理スレッド。
  3. Q2 から読み取り、ディスクに書き込むライター スレッド。

もちろん、出力ファイルが入力ファイルとは物理的に異なるディスク上にあることも確認します。

処理が I/Oよりも遅くなる傾向がある場合(キューのサイズを監視する)、データの読み取りと書き込みの方法が同期されている 2 つ以上の並列 "プロセッサ" で実験を開始できます。

于 2013-07-14T21:35:54.060 に答える
13
  • ファイルの読み取りから抽象化する必要があります。ファイルを読み取り、コンテンツをさまざまな数のスレッドにディスパッチするクラスを作成します。

クラスは文字列をディスパッチすべきではありません。元のシーケンスを維持したいので、行番号などのメタ情報Lineを含むクラスでそれらをラップする必要があります。

  • 収集されたデータに対して実際の作業を行う処理クラスが必要です。あなたの場合、やるべきことはありません。クラスは情報を保存するだけで、いつかそれを拡張して追加の処理を行うことができます (たとえば、文字列を逆にする、他の文字列を追加するなど)。

  • 次に、処理スレッドである種の多方向マージソートを行い、インスタンスへのすべての参照を順番に収集するマージクラスが必要です。 Line

合併クラスは、データをファイルに書き戻すこともできますが、コードをきれいに保つために...

  • 出力クラスを作成することをお勧めします。これは、すべてのファイル処理などから再び抽象化します。

もちろん、メインメモリが不足している場合、このアプローチには多くのメモリが必要です。メモリのオーバーヘッドを小さく保つために、そので機能するストリームベースのアプローチが必要です。


UPDATEストリームベースのアプローチ

以下を除いて、すべて同じままです。

スレッドはReader、読み取ったデータをBalloon. このバルーンには、保持できる特定の数のLineインスタンスがあります (数が大きいほど、より多くのメイン メモリを消費します)。

処理スレッドLineはバルーンから s を取得し、リーダーはバルーンが空になるにつれて、より多くの行をバルーンに送り込みます。

マージ クラスは、上記のように処理スレッドから行を取得し、ライターはデータをファイルに書き戻します。

I/O スレッドで使用する必要があるかもしれません。これはFileChannel、大きなファイルの読み取りにより適しており、ファイルの処理中に消費するメモリが少ないためです (ただし、これは推測にすぎません)。

于 2013-07-19T13:41:02.890 に答える
3

考えられる方法の 1 つは、入力ファイルを読み取り、読み取った行をブロッキング キューに入れる単一のスレッドを作成することです。複数のスレッドがこのキューからのデータを待機し、データを処理します。

別の可能な解決策は、ファイルをチャンクに分割し、各チャンクを個別のスレッドに割り当てることです。

ブロックを回避するには、非同期 IO を使用できます。パターン指向ソフトウェア アーキテクチャ ボリューム 2 のProactor パターンも参照してください。

于 2013-07-14T06:52:40.840 に答える
2

スレッドの理想的な数は、ハードウェア アーキテクチャやその他のものによって制限されることに注意してください (スレッド プールを参照して、最適なスレッド数を計算することを検討してください)。「10」が良い数であると仮定して、先に進みます。=)

パフォーマンスを探している場合は、次のことができます。

  • 持っているスレッドを使用してファイルを読み取り、ビジネス ルールに従ってそれぞれを処理します。出力ファイルに挿入される次の予期される行を示す 1 つの制御変数を保持します。

  • 次の予想される行の処理が完了したら、それをバッファー (キュー) に追加します (出力ファイルに直接挿入する方法を見つけることができれば理想的ですが、ロックの問題が発生します)。それ以外の場合は、この「将来の」行をバイナリ検索ツリー内に格納し、ツリーを行位置で並べます。Binary-search-tree は、検索と挿入に "O(log n)" の時間計算量を提供します。これは、コンテキストに対して非常に高速です。次の「予期される」行の処理が完了するまで、ツリーを埋め続けます。

出力ファイルを開き、定期的にバッファを消費し、ファイルに行を書き込むスレッドをアクティブにします。

また、ファイルに挿入される BST の「マイナー」予想ノードを追跡します。これを使用して、検索を開始する前に将来の行が BST 内にあるかどうかを確認できます。

  • 次の予想される行の処理が完了したら、Queue に挿入し、次の要素が二分探索ツリー内にあるかどうかを確認します。次の行がツリー内にある場合は、ツリーからノードを削除し、ノードの内容をキューに追加して、次の行が既にツリー内にある場合は検索を繰り返します。
  • すべてのファイルの処理が終了し、ツリーが空になり、キューが空になるまで、この手順を繰り返します。

このアプローチでは、 - O(n) を使用してファイルを読み取ります (ただし、並列化されています) - O(1) を使用して順序付けられた行をキューに挿入します - O(Logn)*2 を使用してバイナリ検索ツリーを読み書きします - O( n) 新しいファイルを書き込む

さらに、ビジネス ルールと I/O 操作のコストがかかります。

それが役に立てば幸い。

于 2013-07-23T03:27:27.077 に答える
1

春のバッチが思い浮かびます。

順序を維持するには、後処理ステップが必要です。つまり、順序付けされた読み取りインデックス/キーを処理コンテキストに格納します。処理ロジックは、処理された情報もコンテキストに格納する必要があります。処理が完了したら、リストを後処理してファイルに書き込むことができます。 .

ただし、OOM の問題には注意してください。

于 2013-07-22T11:40:48.797 に答える
0

順序を維持する必要があるため、問題自体は、読み取りと書き込みは順次処理であるため、並行して実行できないということです。並行して実行できるのはレコードの処理だけですが、これもライターが 1 つだけではあまり解決しません。 .

設計案は次のとおりです。

  1. One Thread t1 を使用してファイルを読み取り、データを LinkedBlockingQueue Q1 に格納する
  2. 別のスレッド t2 を使用して Q1 からデータを読み取り、別の LinkedBlockingQueue Q2 に入れます
  3. スレッド t3 は Q2 からデータを読み取り、ファイルに書き込みます。
  4. OutofMemoryError が発生しないようにするには、キューを適切なサイズで初期化する必要があります
  5. CyclicBarrier を使用して、すべてのスレッドが操作を完了できるようにすることができます
  6. さらに、後処理タスクを実行できる CyclicBarrier に Action を設定できます。

頑張って、最高のデザインを手に入れてください。

乾杯 !!

于 2013-07-19T21:03:31.720 に答える
0

私は過去に同様の問題に直面しました。単一のファイルからデータを読み取って処理し、結果を他のファイルに書き込む必要がある場合。加工部分が重かったので。だから私は複数のスレッドを使用しようとしました。私の問題を解決するために私が従ったデザインは次のとおりです。

  • メインプログラムをマスターとして使用し、ファイル全体を一度に読み取ります (ただし、処理を開始しないでください)。行ごとに 1 つのデータ オブジェクトをそのシーケンス順序で作成します。
  • 1 つのプライオリティブロッキングキューを使用して、たとえばメインのキューに、これらのデータ オブジェクトを追加します。すべてのスレッドのコンストラクターでこのキューの参照を共有します。
  • このキューをリッスンするさまざまな処理ユニット、つまりスレッドを作成します。このキューにデータ オブジェクトを追加するときは、notifyall メソッドを呼び出します。すべてのスレッドは個別に処理されます。
  • 処理後、すべての結果を 1 つのマップに配置し、キーをシーケンス番号として結果を配置します。
  • キューが空で、すべてのスレッドがアイドル状態の場合、処理が完了したことを意味します。スレッドを停止します。マップを反復処理し、結果をファイルに書き込みます
于 2013-07-22T11:08:30.983 に答える