少しユーモラスなタイトルで申し訳ありません。その中で「安全」という言葉の 2 つの異なる定義を使用しています (明らかに)。
私はスレッド化にかなり慣れていません (まあ、私はスレッド化を何年も使用してきましたが、非常に単純な形式しか使用していません)。現在、いくつかのアルゴリズムの並列実装を作成するという課題に直面しており、スレッドは同じデータで動作する必要があります。次の初心者の間違いを考えてみましょう。
const
N = 2;
var
value: integer = 0;
function ThreadFunc(Parameter: Pointer): integer;
var
i: Integer;
begin
for i := 1 to 10000000 do
inc(value);
result := 0;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
threads: array[0..N - 1] of THandle;
i: Integer;
dummy: cardinal;
begin
for i := 0 to N - 1 do
threads[i] := BeginThread(nil, 0, @ThreadFunc, nil, 0, dummy);
if WaitForMultipleObjects(N, @threads[0], true, INFINITE) = WAIT_FAILED then
RaiseLastOSError;
ShowMessage(IntToStr(value));
end;
初心者は、上記のコードでメッセージが表示されることを期待するかもしれません20000000
。確かに、最初value
は に等しく0
、次にそれをinc
掛け20000000
ます。ただし、inc
プロシージャは「アトミック」ではないため、2 つのスレッドが競合し (読み取り、インクリメント、保存の 3 つのことを行うと思いinc
ます)、多くのinc
s が事実上「失われます」。上記のコードから得られる典型的な値は10030423
.
最も簡単な回避策は、InterlockedIncrement
代わりに ofを使用することですInc
(このばかげた例でははるかに遅くなりますが、それは重要ではありません)。もう 1 つの回避策はinc
、クリティカル セクション内に配置することです (はい、このばかげた例でも非常に遅くなります)。
現在、ほとんどの実際のアルゴリズムでは、競合はそれほど一般的ではありません。実際、それらは非常に珍しいかもしれません。私のアルゴリズムの 1 つはDLA フラクタルを作成し、変数の 1 つはinc
吸着粒子の数です。ここでの競合は非常にまれであり、さらに重要なことに、変数の合計が 20000000、20000008、20000319、または 19999496 になるかどうかは気にしません。したがって、コードを肥大化させるだけなので、クリティカル セクションを使用しないInterlockedIncrement
ように誘惑されます。そして、(わずかに)遅くなり、(私が見る限り)メリットがありません。
しかし、私の質問は次のとおりです。インクリメント変数のわずかに「間違った」値よりも、競合のより深刻な結果が生じる可能性がありますか? たとえば、プログラムがクラッシュする可能性はありますか?
確かに、この質問はばかげているように思えるかもしれません。結局のところ、InterlockedIncrement
代わりに使用するコストinc
はかなり低いため (多くの場合、すべてではありません!)、安全にプレイしないのは (おそらく) ばかげているからです。しかし、これが実際に理論レベルでどのように機能するかを知ることは良いことだとも感じているので、この質問は依然として非常に興味深いと思います.