1

System.ServiceModel.Channels.BufferManager のソース コードを見ていると、次のメソッドに気付きました。

void TuneQuotas()
{
    if (areQuotasBeingTuned)
        return;

    bool lockHeld = false;
    try
    {
        try { }
        finally
        {
            lockHeld = Monitor.TryEnter(tuningLock);
        }

        // Don't bother if another thread already has the lock
        if (!lockHeld || areQuotasBeingTuned)
            return;
        areQuotasBeingTuned = true;
    }
    finally
    {
        if (lockHeld)
        {
            Monitor.Exit(tuningLock);
        }
    }
    //
    // DO WORK... (code removed for brevity)
    //
    areQuotasBeingTuned = false;
}

明らかに、実行するスレッドは 1 つだけでTuneQuotas()、別のスレッドによって既に実行されている場合、他のスレッドは待機しません。削除されたコードは保護されていないことに注意してください。

これを行うだけでなく、上記のこの方法の利点を理解しようとしています:

void TuneQuotas()
{
    if(!Monitor.TryEnter(tuningLock)) return;
    //
    // DO WORK...
    //
    Monitor.Exit(tuningLock);
}

なぜ彼らがそのすべてに悩まされたのか、何か考えはありますか? 彼らがブロックを使用する方法は、スレッドの中止シナリオを防ぐことだと思いますが、このコードをすべて使用しても、その 1 つのスレッドがすべてを実行しない場合、永久にロックされるfinallyため、まだ要点がわかりません。なんらかの理由で、set までの道のり。では、このパターンについて私が見逃しているクールなものはありますか?TuneQuotas()areQuotasBeingTunes=false

EDIT: 補足として、フレームワーク4で実行されているこのコードを使用して確認した.NET 4.0にメソッドが存在するようです(ただし、メソッドの内容がWebで見つけたものから変更されていないことは確認できません) :

var buffMgr = BufferManager.CreateBufferManager(1, 1);
var pooledBuffMgrType = buffMgr.GetType()
    .GetProperty("InternalBufferManager")
    .GetValue(buffMgr, null)
    .GetType();

Debug.WriteLine(pooledBuffMgrType.Module.FullyQualifiedName);
foreach (var methodInfo in pooledBuffMgrType
    .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic))
{
    Debug.WriteLine(methodInfo.Name);
}

出力:

C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.DurableInstancing\v4.0_4.0.0.0__3    1bf3856ad364e35\System.Runtime.DurableInstancing.dll
ChangeQuota
DecreaseQuota
FindMostExcessivePool
FindMostStarvedPool
FindPool
IncreaseQuota
TuneQuotas
Finalize
MemberwiseClone
4

3 に答える 3

1

コメントを追加します:

void TuneQuotas()
{
    if (areQuotasBeingTuned)
        return; //fast-path, does not require locking

    bool lockHeld = false;
    try
    {
        try { }
        finally
        {
            //finally-blocks cannot be aborted by Thread.Abort
            //The thread could be aborted after getting the lock and before setting lockHeld
            lockHeld = Monitor.TryEnter(tuningLock);
        }

        // Don't bother if another thread already has the lock
        if (!lockHeld || areQuotasBeingTuned)
            return; //areQuotasBeingTuned could have switched to true in the mean-time
        areQuotasBeingTuned = true; //prevent others from needlessly trying to lock (trigger fast-path)
    }
    finally //ensure the lock being released
    {
        if (lockHeld)
        {
            Monitor.Exit(tuningLock);
        }
    }
    //
    // DO WORK... (code removed for brevity)
    //
    //this might be a bug. There should be a call to Thread.MemoryBarrier,
    //or areQuotasBeingTuned should be volatile
    //if not, the write might never reach other processor cores
    //maybe this doesn't matter for x86
    areQuotasBeingTuned = false;
}

あなたが与えた単純なバージョンは、いくつかの問題から保護しません。少なくとも、例外安全ではありません(ロックは解放されません)。興味深いことに、「洗練された」バージョンもそうではありません。

このメソッドは.NET4から削除されました。

于 2012-08-24T19:17:27.780 に答える
1

.NET 4.0 までは、基本的にステートメントによって生成されたコードにバグがありましたlock。次のようなものが生成されます。

Monitor.Enter(lockObject)
// see next paragraph
try
{
    // code that was in the lock block
}
finally
{
   Monitor.Exit(lockObject);
}

これは、 と の間Enterで例外が発生した場合tryExitが呼び出されないことを意味します。usr がほのめかしたように、これはThread.Abort.

あなたの例:

if(!Monitor.TryEnter(tuningLock)) return;
//
// DO WORK...
//
Monitor.Exit(tuningLock);

この問題などに苦しんでいます。このコード と が中断されてExit呼び出されないウィンドウは、基本的にコードのブロック全体であり、( からの 1 つだけでなくThread.Abort) あらゆる例外によるものです。

ほとんどのコードが .NET で書かれている理由がわかりません。しかし、このコードは と の間の例外の問題を回避するために書かれたものだと思いEnterますtry。詳細のいくつかを見てみましょう。

try{}
finally
{
  lockHeld = Monitor.TryEnter(tuningLock);
}

Finallyブロックは基本的に IL で制約付き実行領域を生成します。制約付き実行領域は、何によっても中断できません。したがって、上記TryEnterfinallyブロックに を配置すると、lockHeld確実に の状態が保持されlockます。

そのコード ブロックは、ステートメントが true の場合を呼び出すtry/finallyブロックに含まれています。これは、とブロックの間に中断できる ポイントがないことを意味します。finallyMonitor.ExittuningLockEntertry

FWIW、このメソッドはまだ .NET 3.5 にあり、(.NET ソース コードではなく) WCF 3.5 ソース コードに表示されます。4.0 の内容はまだわかりません。でも同じだと思います。その構造の一部に対する原動力がもはや存在しなくなったとしても、動作するコードを変更する理由はありません。

生成にlock 使用されたものの詳細については、http://blogs.msdn.com/b/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspxを参照してください。

于 2012-08-25T13:59:49.617 に答える
0

なぜ彼らがそれらすべてに悩まされたかもしれないという考えはありますか?

いくつかのテストを実行した後、1つの理由がわかります(理由ではないにしても):それははるかに高速であるため、おそらくそれらすべてに悩まされていました!

オブジェクトがすでにロックされMonitor.TryEnterている場合は、コストのかかる呼び出しであることがわかります(ロックされていない場合でも、非常に高速です。問題はありません)。したがって、最初のスレッドを除くすべてのスレッドで速度が低下します。TryEnter

これはそれほど重要だとは思いませんでした。結局のところ、各スレッドは一度だけロックを取得してから次に進みます(そこに座ってループで試行するのとは異なります)。しかし、私は比較のためにいくつかのコードを書きました、そしてそれはTryEnter(すでにロックされているとき)のコストがかなりのものであることを示しました。実際、私のシステムでは、デバッガーを接続しない場合、各呼び出しに約0.3ミリ秒かかりました。これは、単純なブールチェックを使用する場合よりも数桁遅くなります。

おそらく、これはMicrosoftのテスト結果に現れたので、ファストトラックブールチェックを追加することで、上記のようにコードを最適化したのではないかと思います。しかし、それは私の推測です。

于 2012-08-25T14:10:00.707 に答える