以下の参照スレッドに対して、GC.KeepAlive() にコメントしても違いはなく、他のインスタンスの作成をブロックします。なぜ著者はその重要な行を明示的に言及したのですか?
3 に答える
更新: この質問は、2013 年 6 月の私のブログの主題でした。この件に関するより多くの考えについては、その記事を参照してください。素晴らしい質問をありがとう!
ここで何が起こっているのかを明確にしましょう。それがミューテックスであるという事実を無視して、一般的なケースを考えてみましょう:
class Foo
{
public Foo()
{
this.x = whatever;
this.y = whatever;
SideEffects.Alpha(); // Does not use "this"
}
~Foo()
{
SideEffects.Charlie();
}
...
}
static class SideEffects
{
public static void Alpha() {...}
public static void Bravo() {...}
public static void Charlie() {...}
public static void M()
{
Foo foo = new Foo(); // Allocating Foo has side effect Alpha
Bravo();
// Foo's destructor has side effect Charlie
}
}
著者は、M
その副作用Alpha()
、Bravo()
およびCharlie()
その順序で起こることを望んでいます。同じスレッドで発生するため、Alpha()
前に発生する必要があると考えるかもしれません。C# は、同じスレッドで発生する処理が副作用の順序を保持することを保証します。あなたは正しいでしょう。Bravo()
Alpha()
Bravo()
に格納されている参照がガベージ コレクションされるまで発生せず、 の呼び出し後、制御が のスコープを離れるまでローカル変数はその参照を保持するため、Charlie()
後で発生する必要があると考えることができます。 これは間違っています。C# コンパイラと CLR jit コンパイラは、ローカル変数を早期に「無効」と宣言できるように連携することが許可されています。C# は、1 つのスレッドから観察したときに、予測可能な順序で処理が行われることのみを保証することに注意してください。ガベージ コレクターとファイナライザーは独自のスレッドで実行されます。そのため、ガベージ コレクターが (で使用されていない)が呼び出される前に死んでいると推測することは合法です。Bravo()
Charlie()
foo
foo
foo
Bravo()
foo
Bravo()
Bravo()
したがって、別のスレッドで、Charlie()
前Bravo()
または実行中 に副作用が発生する可能性があります。Bravo()
AKeepAlive(foo)
は、コンパイラがこの最適化を行うのを防ぎます。foo
少なくとも まで生きていることをガベージコレクターに伝えます。KeepAlive
したがって、Charlie()
ファイナライザーの副作用は side effect の後に来ることが保証されますBravo()
。
ここで興味深い質問があります。キープアライブがないと、以前に副作用がCharlie()
発生する可能性がありますか? 場合によっては、そうです!ジッターは非常に積極的にすることができます。ctor が終了するとすぐに ctor の が死んでいることを発見した場合、 ctor が のフィールドの変更を停止した直後にコレクションをスケジュールすることができます。Alpha()
this
this
this
KeepAlive(this)
コンストラクターで aを実行しない限り、ガベージ コレクターは実行前Foo()
に収集することが許可されます。(もちろん、それを維持するものは他に何もないという前提で。)オブジェクトは、そのコンストラクターがまだ別のスレッドで実行されている間にファイナライズされる場合があります。これが、デストラクタを持つクラスを非常に慎重に作成 する必要があるもう 1 つの理由です。ジッターが激しいため、デストラクタが突然別のスレッドで予期せず呼び出された場合に備えて、オブジェクト全体を堅牢に設計する必要があります。this
Alpha()
特定のケースでは、「アルファ」はミューテックスを取り出すことの副作用、「ブラボー」はプログラム全体を実行することの副作用、「チャーリー」はミューテックスを解放することの副作用です。キープアライブがないと、プログラムの実行前、または実行中にミューテックスを解放できます。リリースする必要はなく、ほとんどの場合、リリースされません。しかし、ジッターがゴミを出すことに積極的になることを決定した場合、それは可能性があります。
いくつかの代替手段は何ですか?
キープアライブは正しいですが、個人的には元のコードではキープアライブを選択しません。基本的には次のとおりです。
static void Main()
{
Mutex m = GetMutex();
Program.Run();
GC.KeepAlive(m);
}
別の方法は次のとおりです。
static Mutex m;
static void Main()
{
m = GetMutex();
Program.Run();
}
ジッターは、ローカル変数を早期にキルすることは許可されていますが、静的変数を早期にキルすることは許可されていません。したがって、KeepAlive は必要ありません。
Mutex が使い捨てであるという事実を利用するのがさらに良いでしょう:
static void Main()
{
using(GetMutex())
Program.Run();
}
これは短い書き方です:
static void Main()
{
Mutex m = GetMutex();
try
{
Program.Run();
}
finally
{
((IDisposable)m).Dispose();
}
}
そして今、ガベージ コレクターは、制御が終了した後にミューテックスを破棄する必要があるため、早い段階でミューテックスをクリーンアップできませんRun
。(ジッターがミューテックスの破棄が何もしないことを証明できる場合は、早期にクリーンアップすることが許可されますが、この場合、ミューテックスの破棄は効果があります。)
これを行わないと、ガベージコレクションでミューテックスが破棄されますが、これはすぐに発生することが保証されたイベントではないため、長期間機能する可能性があります。
私は静的な自分自身を使用しただろう2番目の答えを参照してください。
冗長であることを証明できるテストの数はありませんがGC.KeepAlive
、必要であることを証明できるのは 1 つの (失敗した) テストのみです。
つまり、GC.KeepAlive
を省略すると、コードが正しく機能しない可能性があります。すぐに壊れるとは限りません。mutex
また、スコープ外になるローカル変数であるため、正しく機能しない可能性があります。これにより、ガベージ コレクションの対象になります。GC がそのオブジェクトを収集することを決定した場合、ミューテックスは解放されます (そして、新しいインスタンスを起動できるようになります)。