1

動的コンテンツがたくさんあるASP.NETアプリケーションがあります。内容は、特定のクライアントに属するすべてのユーザーで同じです。リクエストごとに必要なデータベースヒットの数を減らすために、クライアントレベルのデータをキャッシュすることにしました。データを保持する静的クラス( "ClientCache")を作成しました。
このクラスで最もよく使用されるメソッドは「GetClientData」です。これは、特定のクライアントに保存されているすべてのデータを含むClientDataオブジェクトを返します。ただし、ClientDataは遅延してロードされます。要求されたクライアントデータがすでにキャッシュされている場合、呼び出し元はキャッシュされたデータを取得します。それ以外の場合は、データがフェッチされ、キャッシュに追加されてから、呼び出し元に返されます。

最終的に、ClientDataオブジェクトがキャッシュに追加された行のGetClientDataメソッドで断続的なクラッシュが発生し始めました。メソッド本体は次のとおりです。

public static ClientData GetClientData(Guid fk_client)
{
    if (_clients == null)
        _clients = new Dictionary<Guid, ClientData>();

    ClientData client;
    if (_clients.ContainsKey(fk_client))
    {
        client = _clients[fk_client];
    }
    else
    {
        client = new ClientData(fk_client);
        _clients.Add(fk_client, client);
    }
    return client;
}

例外テキストは常に「同じキーを持つオブジェクトがすでに存在します」のようなものです。もちろん、クライアントがすでに存在する場合は、クライアントをキャッシュに追加できないようにコードを記述しようとしました。

この時点で、競合状態が発生し、メソッドが2回同時に実行されている可能性があります。これにより、コードがどのようにクラッシュするかを説明できます。しかし、私が混乱しているのは、メソッドを2回同時に実行する方法です。私の知る限り、ASP.NETアプリケーションは一度に1つの要求しか処理しません(そのため、HttpContext.Currentを使用できます)。

それで、このバグは、クリティカルセクションにロックをかける必要がある競合状態である可能性がありますか?それとも私はもっと明白なバグを見逃していますか?

4

4 に答える 4

3

ASP.NET アプリケーションが一度に 1 つの要求しか処理しない場合、すべての ASP.NET サイトが深刻な問題に陥ります。ASP.NET は、一度に数十を処理できます (通常、CPU コアあたり 25)。

独自の辞書を使用してオブジェクトを格納する代わりに、ASP.NET キャッシュを使用する必要があります。キャッシュに対する操作はスレッドセーフです。

キャッシュに保存するオブジェクトの読み取り操作がスレッド セーフであることを確認する必要があることに注意してください。

編集

この回答へのコメントは次のように述べています:-

キャッシュ上のアトミック操作のみがスレッドセーフです。キーが存在するかどうかを確認し
てから追加すると、スレッドセーフではなく、アイテムが
上書きされる可能性があります。

そのような操作をアトミックにする必要があると感じた場合、キャッシュはおそらくリソースの適切な場所ではないことを指摘する価値があります。

コメントの説明とまったく同じように動作するコードがかなりあります。ただし、保存されるリソースは両方の場所で同じになります。したがって、まれに既存のアイテムが上書きされた場合、唯一のコストは、1 つのスレッドが不必要にリソースを生成したことです。このまれなイベントのコストは、アクセスが試行されるたびに操作をアトミックにしようとするコストよりもはるかに小さくなります。

于 2009-02-21T21:53:33.010 に答える
2

これは非常に簡単に修正できます。

private _clientsLock = new Object();

public static ClientData GetClientData(Guid fk_client)
{
  if (_clients == null)
    lock (_clientsLock)
      // Check again because another thread could have created a new 
      // dictionary in-between the lock and this check
      if (_clients == null) 
        _clients = new Dictionary<Guid, ClientData>();

  if (_clients.ContainsKey(fk_client))
    // Don't need a lock here UNLESS there are also deletes. If there are
    // deletes, then a lock like the one below (in the else) is necessary
    return _clients[fk_client];
  else
  {
    ClientData client = new ClientData(fk_client);

    lock (_clientsLock)
      // Again, check again because another thread could have added this
      // this ClientData between the last ContainsKey check and this add
      if (!clients.ContainsKey(fk_client))
       _clients.Add(fk_client, client);

    return client;
  }
}

静的クラスをいじると、スレッド同期の問題が発生する可能性があることに注意してください。ある種の静的なクラス レベルのリスト (この場合は _clients、オブジェクトDictionary) がある場合、対処すべきスレッド同期の問題が確実に発生します。

于 2009-02-22T01:10:25.567 に答える
0

あなたのコードは、一度に関数内にスレッドが 1 つしかないことを実際に想定しています。

これは、ASP.NET では当てはまりません。

この方法を主張する場合は、静的セマフォを使用して、このクラスの周囲の領域をロックしてください。

于 2009-02-21T22:02:34.550 に答える
0

スレッドセーフとロックの最小化が必要です。
二重チェックのロックを参照してください ( http://en.wikipedia.org/wiki/Double-checked_locking )

TryGetValue で簡単に記述します。


public static object lockClientsSingleton = new object();

public static ClientData GetClientData(Guid fk_client)
{
    if (_clients == null) {
        lock( lockClientsSingleton ) {
            if( _clients==null ) {
                _clients = new Dictionary``();
            }
        }
    }
    ClientData client;
    if( !_clients.TryGetValue( fk_client, out client ) )
    {
        lock(_clients) 
        {
            if( !_clients.TryGetValue( fk_client, out client ) ) 
            {
                client = new ClientData(fk_client)
                _clients.Add( fk_client, client );
            }
        }
    }
    return client;
}
于 2009-10-19T02:48:22.460 に答える