5

これが取引です。私のアプリには、同じことを行うスレッドがたくさんあります-巨大なファイル(> 2GB)から特定のデータを読み取り、データを解析し、最終的にそのファイルに書き込みます。

問題は、1 つのスレッドがファイル A から X を読み取り、2 番目のスレッドが同じファイル A の X に書き込むことが時々発生する可能性があることです。問題は発生しますか?

I/O コードは、すべてのファイルに TFileStream を使用します。問題が発生するのではないかと心配して、I/O コードをローカル (静的クラス) に分割しました。分割されているため、重要なセクションがあるはずです。

以下のすべてのケースは、インスタンス化されていないローカル (静的) コードです。

ケース 1:

procedure Foo(obj:TObject);
begin ... end;

ケース 2:

procedure Bar(obj:TObject);
var i: integer;
begin
  for i:=0 to X do ...{something}
end;

ケース 3:

function Foo(obj:TObject; j:Integer):TSomeObject
var i:integer;
begin
  for i:=0 to X do
    for j:=0 to Y do
      Result:={something}
end;

質問 1: 1 つ以上のスレッドが同時に呼び出しても問題がないように、クリティカル セクションが必要なのはどのような場合ですか?

質問 2: スレッド 1 がファイル A から X(エントリ) を読み取り、スレッド 2 がファイル A の X(エントリ) に書き込む場合、問題は発生しますか?

クリティカル セクションはいつ使用する必要がありますか? 私はそれを私の頭で想像しようとしますが、それは難しいです-スレッドは1つだけです:))

編集

これで合っているでしょうか?

{2GB ファイルごとのクラス}

TSpecificFile = class
  cs: TCriticalSection;
  ...
end;

TFileParser = class
  file :TSpecificFile;
  void Parsethis; void ParseThat....
end;

function Read(file: TSpecificFile): TSomeObject;
begin
  file.cs.Enter;
  try
    ...//read
  finally
    file.cs.Leave;
  end;
end;

function Write(file: TSpecificFile): TSomeObject;
begin
  file.cs.Enter;
  try
    //write
  finally
    file.cs.Leave
  end;
end;

2 つのスレッドが Read を呼び出した場合、問題が発生します:

ケース 1: 同じ TSpecificFile

ケース 2: 異なる TSpecificFile?

別のクリティカル セクションが必要ですか?

4

3 に答える 3

7

一般に、複数のスレッドが同時に共有リソースにアクセスする可能性があり、少なくとも 1 つのスレッドが共有リソースに書き込み/変更を行う場合は常に、ロック メカニズム (クリティカル セクションはロック メカニズムです) が必要です。
これは、リソースがメモリ内のオブジェクトであるか、ディスク上のファイルであるかに関係なく当てはまります。
また、ロックが必要な理由は、読み取り操作が書き込み操作と同時に発生した場合、読み取り操作で一貫性のないデータが取得され、予期しない動作が発生する可能性があるためです。
Stephen Cheung は、ファイル処理に関するプラットフォーム固有の考慮事項について言及しているので、ここでは繰り返しません。

補足として、あなたの場合に適用される可能性のある別の同時実行の問題を強調したいと思います。

  • 1 つのスレッドがデータを読み取り、処理を開始するとします。
  • 次に、別のスレッドが同じことを行います。
  • 両方のスレッドが、結果をファイル A の位置 X に書き込む必要があると判断します。
  • せいぜい書き込まれる値は同じであり、スレッドの 1 つが事実上何もせずに時間を無駄にしました。
  • 最悪の場合、スレッドの 1 つの計算が上書きされ、結果が失われます。

これがアプリケーションにとって問題になるかどうかを判断する必要があります。もしそうなら、読み書き操作をロックするだけでは解決しないことを指摘しなければなりません。さらに、ロックの期間を延長しようとすると、別の問題が発生します。

オプション

クリティカル セクション

はい、クリティカル セクションを使用できます。

  • クリティカル セクションの最適な粒度 (ファイル全体に 1 つ) を選択する必要があります。ファイル内の特定のブロックを指定するために使用することもできます。
  • この決定には、あなたのアプリケーションが何をするのかをよりよく理解する必要があるため、私はあなたに代わって答えるつもりはありません.
  • デッドロックの可能性に注意してください。
    • スレッド 1 がロック A を取得する
    • スレッド 2 はロック B を取得します
    • スレッド 1 はロック B を要求しますが、待機する必要があります
    • スレッド 2 はロック A を要求します。どちらのスレッドも取得したロックを解放できないため、デッドロックが発生します。

また、ソリューションで考慮すべき他の 2 つのツールも提案します。

シングルスレッド

何と言う衝撃的なことでしょう!しかし、真剣に、マルチスレッド化する理由が「アプリケーションを高速化するため」である場合、間違った理由でマルチスレッド化したことになります。それを行うほとんどの人は、実際にアプリケーションを作成することになり、書くのが難しくなり、信頼性が低くなり、遅くなります!

複数のスレッドがアプリケーションを高速化するというのは、あまりにも一般的な誤解です。タスクの実行に X クロック サイクルが必要な場合、X クロック サイクルかかります。複数のスレッドはタスクを高速化するわけではなく、複数のタスクを並行して実行できます。しかし、これは悪いことかもしれません! ...

あなたのアプリケーションは、ディスクからの読み取り、読み取り内容の解析、およびディスクへの書き込みに大きく依存していると説明しました。解析ステップの CPU 集中度によっては、すべてのスレッドがほとんどの時間をディスク IO 操作の待機に費やしていることに気付く場合があります。その場合、通常、複数のスレッドは、ディスク ヘッドを (うーん丸い) ディスク プラッターの遠い「隅」にシャントするためだけに機能します。ディスク IO は依然としてボトルネックであり、スレッドによって、ファイルが最大限に断片化されているかのように動作します。

キューイング操作

マルチスレッド化の理由が正当であり、共有リソースで動作するスレッドがまだあるとします。同時実行の問題を回避するためにロックを使用する代わりに、共有リソース操作を特定のスレッドのキューに入れることができます。

したがって、スレッド 1 の代わりに:

  • ファイル A から位置 X を読み取る
  • データの解析
  • ファイル A の位置 Y への書き込み

別のスレッドを作成します。FileA スレッド:

  • FileA には命令のキューがあります
  • 位置 X を読み取る命令に到達すると、そうします。
  • データをスレッド 1 に送信します。
  • スレッド 1 はそのデータを解析します --- FileA スレッドは命令の処理を続けます
  • スレッド 1 は、FileA スレッドのキューの後ろにある位置 Y にその結果を書き込む命令を配置します --- FileA スレッドは他の命令の処理を続けます。
  • 最終的に、FileA スレッドは、Trhead 1 の要求に従ってデータを書き込みます。
于 2011-03-19T11:28:59.797 に答える
5

同期は、複数のエージェントが何かを行っている場合に問題 (またはエラー) を引き起こす可能性のある共有データに対してのみ必要です。

明らかに、ファイル書き込み操作は、書き込みが完了する前に他の書き込みプロセスが新しいデータを踏みにじるのを望まない場合にのみ、そのファイルのクリティカル セクションにラップする必要があります。新しいデータの残りの半分 (元の書き込みプロセスによってまだ書き出されていない) を認識しない別のプロセスによって変更された新しいデータ。したがって、ファイルごとに 1 つずつ、CS のコレクションが作成されます。そのCSは、書き終わったらすぐにリリースする必要があります。

メモリ マップド ファイルやスパース ファイルなどの特定のケースでは、O/Sにより、ファイルの異なる部分に同時に書き込むことができる場合があります。したがって、そのような場合、CS はファイルの特定のセグメントにある必要があります。したがって、ファイルごとに CS のコレクション (セグメントごとに 1 つ) があります。

ファイルへの書き込みと読み取りを同時に行うと、リーダーは一貫性のないデータを取得する可能性があります。一部の O/S では、書き込みと同時に読み取りを行うことができます (おそらく読み取りはキャッシュされたバッファーから行われます)。ただし、ファイルへの書き込みと読み取りを同時に行うと、読み取った内容が正しくない場合があります。読み取りに関する一貫したデータが必要な場合は、リーダーもクリティカル セクションの対象となる必要があります。

場合によっては、あるセグメントに書き込み、別のセグメントから読み取る場合、O/S がそれを許可することがあります。ただし、これが正しいデータを返すかどうかは通常保証できません。ファイルの 2 つのセグメントが 1 つのディスク セクターに存在するかどうか、またはその他の低レベルの O/S のものであるかどうかを常に判断できるとは限らないためです。

したがって、一般的には、ファイル操作をファイルごとに CS でラップすることをお勧めします。

理論的には、同じファイルから同時に読み取ることができるはずですが、CS でロックすると 1 つのリーダーしか許可されません。その場合、実装を「読み取りロック」と「書き込みロック」に分離する必要があります (データベース システムと同様)。ただし、さまざまなレベルのロックの昇格に対処する必要があるため、これは非常に重要です。

注: データにしようとしている種類のこと (サイズが GB の巨大なデータ セットを同時にセグメントで読み書きすること) は、データベースで一般的に行われていることです。データファイルをデータベースレコードに分割することを検討する必要があります。そうしないと、ロックが原因で読み取り/書き込みのパフォーマンスが最適化されないか、リレーショナル データベースを再発明することになります。

于 2011-03-19T09:13:22.767 に答える
3

結論から先に

必要ありませんTCriticalSection。ブロックすることなく、2 つのスレッドが同じデータを処理しないことを保証するキューベースのアルゴリズムを実装する必要があります。

その結論に至った経緯

まず第一にWindows(Win 7?) では、ファイルに何度でも同時に書き込むことができます。それが書き込みで何をするのかわかりませんし、それが良い考えだと言っているわけでもありませんが、Windows が同じファイルへの同時複数書き込みを許可することを証明するために、次のテストを行ったところです。

書き込み用にファイルを開き(「share deny none」を使用)、ランダムなものをランダムなオフセットに30秒間書き込み続けるスレッドを作成しました。コードを含むペーストビンを次に示します。

TCriticalSection が良くない理由

クリティカル セクションでは、常に 1 つのスレッドのみが保護リソースにアクセスできます。2 つのオプションがあります。読み取り/書き込み操作の間だけロックを保持するか、指定されたリソースの処理に必要な時間全体にわたってロックを保持します。どちらも深刻な問題を抱えています。

スレッドが読み取り/書き込み操作の間だけロックを保持すると、次のようになります。

  • スレッド 1 がロックを取得し、データを読み取り、ロックを解放します
  • スレッド 2 はロックを取得し、同じデータを読み取り、ロックを解放します
  • スレッド 1 が処理を終了し、ロックを取得し、データを書き込み、ロックを解放します
  • スレッド 2 はロックを取得し、データを書き込みます。おっと: スレッド 1 がバックグラウンドで変更を行ったので、スレッド 2 は古いデータに取り組んでいました!

スレッドがラウンド トリム読み取りおよび書き込み操作全体のロックを保持している場合、次のようになります。

  • スレッド 1 がロックを取得し、データの読み取りを開始します
  • スレッド 2 が同じロックを取得しようとすると、ブロックされます...
  • スレッド 1 はデータの読み取りを終了し、データを処理し、データをファイルに書き戻し、ロックを解放します。
  • スレッド 2 はロックを取得し、同じデータの処理を再開します。

キュー ソリューション

あなたはマルチスレッドであり、複数のスレッドが同じファイルからのデータを同時に処理することができるので、データはどういうわけか「コンテキストフリー」であると思います.1番目を処理する前にファイルの3番目の部分を処理できます そうでない場合、マルチスレッド化できない (またはファイルごとに 1 つのスレッドに制限されている) ため、これは true でなければなりません。

処理を開始する前に、次のようないくつかの「ジョブ」を準備できます。

  • ファイル「file1.raw」、オフセット 0、1024 Kb
  • ファイル「file1.raw」、オフセット 1024、1024 kb。
  • ...
  • ファイル「fileN.raw」、オフセット 99999999、1024 kb

それらすべての「ジョブ」をキューに入れます。スレッドがキューから 1 つのジョブをデキューして処理するようにします。2 つのジョブが重複することはないため、スレッドは互いに同期する必要がないため、クリティカル セクションは必要ありません。キュー自体へのアクセスを保護するには、クリティカル セクションのみが必要です。Windows は、スレッドが割り当てられた「ジョブ」に固執している限り、スレッドがファイルに対して適切に読み書きできることを確認します。

于 2011-03-19T10:27:55.557 に答える