これは汎用 PC で実行できます (コードを変更せずにサーバーに拡張できます)。
ロックは問題ありません。ビッグ データに対して時間のかかるタスクを実行する 1 つのライターと少数のコンシューマーを使用すると、ロックの発生はほとんどなく、実質的にロックの競合はゼロになるため、問題にはなりません。
単純なスピンロック (低レイテンシが本当に切望されている場合) またはできればpthread_mutex
(とにかく、ほとんどの場合スピンロックにフォールバックする) からの何でも問題ありません。派手なものはありません。
ロックを取得し、ソケットから 1 メガバイトのデータを受け取り、それをディスクに書き込んで、ロックを解放するわけではないことに注意してください。それはそれがどのように機能するかではありません。1 メガバイトのデータを受け取り、排他的に所有
している領域に書き込み、ロックを取得し、ポインターを変更 (したがって所有権を譲渡) し、ロックを解放します。ロックは、ギガバイト サイズのバッファー内のすべてのバイトではなく、メタデータを保護します。長時間実行されるタスク、短いロック時間、競合 = ゼロ。
実際のデータに関しては、15MiB/s の書き込みはまったく問題なく、通常のハードディスクは 5 ~ 6 倍、SSD はその 10 ~ 20 倍を簡単に実行できます。また、自分で行う必要さえありません。これは、オペレーティング システムに管理を任せることができます。
ディスクとメモリ マップ上に54.1GB 1ファイルを作成します (64 ビット システムであると仮定すると、マルチギガバイト RAM サーバーについて話すときの合理的な仮定であり、これは問題ありません)。残りはオペレーティング システムが処理します。循環バッファー2として使用するマップされた領域にデータを書き込むだけです。
最近書き込まれたものは、多かれ少なかれRAM に常駐することが保証されているため、コンシューマーは障害なくアクセスできます。サーバーに十分な物理 RAM があるかどうかによって、古いデータが RAM にある場合とない場合があります。
古いデータにも引き続きアクセスできますが、速度が若干遅くなる可能性があります (セット全体を常駐させるのに十分な物理 RAM がない場合)。ただし、最近書き込まれたデータを読み取るプロデューサーまたはコンシューマーには影響しません (マシンのスペックが非常に低く、RAM に 1MiB ブロックを 2 ~ 3 個保持することさえできない場合を除きますが、別の問題が発生します! )。
5 つのコンシューマーが存在することを除けば、データをどのように処理するつもりなのかについてはあまり具体的ではないため、この部分についてはあまり詳しく説明しません。ジョブスケジューリングシステムを実装する必要があるかもしれません。または、各受信ブロックを5つの小さなチャンクに分割するか、何をしたいかによって異なります。
いずれの場合も考慮する必要があるのは、マッピングされたリングバッファ内のデータの領域 (ポインタとして、またはマッピングへのオフセットとして) が「有効」であり、領域が「未使用」であることです。
プロデューサはマッピングの所有者であり、コンシューマがメタデータ (オフセットの開始/終了ペア) で指定された境界内のデータにアクセスすることを「許可」します。このメタデータを変更できるのはプロデューサーのみです。このメタデータにアクセスするすべてのユーザー (プロデューサーを含む) は、ロックを取得する必要があります。
おそらくアトミック操作でこれを行うことさえ可能ですが、あなたがめったにロックしない方法を見ると、私は気にしません. ロックを使用するのは非常に簡単であり、微妙な間違いを犯すことはありません。
プロデューサは、コンシューマが明確に定義された境界内のデータのみを参照することを認識しているため、ロックせずに境界外の領域 (「空」であると認識されている領域) に書き込むことができます。後で境界を変更するためにロックする必要があるだけです。
54.1Gib > 54Gib であるため、書き込み可能なマッピングに予備の 1MiB ブロックが 100 個あります。これはおそらく必要以上に多いでしょう (2 つまたは 3 つあれば十分です) が、いくつか余分に持っていても問題はありません。新しいブロックに書き込む (そして有効な範囲を 1 増やす) ときに、「有効な範囲」のもう一方の端も調整します。そうすれば、スレッドは古いブロックにアクセスできなくなりますが、そのブロックでまだ作業しているスレッドは作業を終了できます (データはまだ存在します)。
正確性について厳密である場合、ブロックの処理に非常に長い時間がかかる場合 (この場合は 1 分半以上)、競合状態が発生する可能性があります。絶対に確実にしたい場合は、最悪の場合プロデューサーをブロックする可能性のある別のロックが必要になります。これは絶対に望んでいないことですが、仮想のコンピューターに無制限のメモリがない限り、最悪の場合にプロデューサをブロックすることは、すべての不自然なケースで 100% 正しい唯一のことです。
状況を考えると、この理論上のレースは「許容できる」ものだと思います。1 つのブロックを処理するのに非常に長い時間がかかり、大量のデータが着実に入ってくる場合は、はるかに深刻な問題が発生しているため、実際には問題にはなりません。
将来のある時点で、上司が 1 時間以上のバックログを保持する必要があると判断した場合は、ファイルを拡大して再マップできます。「空の」領域が古いバッファのサイズの最後にある場合は、単に「既知の」ファイルサイズを拡張し、プロデューサーで max_size 値を調整します。コンシューマ スレッドは知る必要さえありません。もちろん、別のファイルを作成し、データをコピーし、スワップし、その間にコンシューマーをブロックしておくこともできますが、それは劣ったソリューションだと思います. サイズの増加がすぐに見えるようにすることはおそらく必要ではありませんが、一方で、「見えない」プロセスであることが非常に望ましいです。
コンピューターに RAM を追加すると、何も変更する必要なく、プログラムが "魔法のように" RAM を使用します。オペレーティング システムは、単純により多くのページを RAM に保持します。さらにいくつかのコンシューマーを追加しても、同じように機能します。
1必要なものよりも意図的に大きいため、「余分な」1MiB ブロックがいくつかあります。
2できればmadvise
、オペレーティング システム (Linux などの破壊的な DONT_NEED ヒントを持つシステムを使用している場合) は、領域を上書きする前にコンテンツに関心がないことを確認できます。しかし、そうしないと、どちらの方法でも機能しますが、書き込み操作で十分だった場所で OS が読み取り、変更、書き込み操作を実行する可能性があるため、効率がわずかに低下します。
3もちろん、実際に保証されることは決してありませんが、とにかくそうなります。