3

Web アプリケーションの一部として IIS にシングルトン サービス クラスがあります (サービスはデータ キャッシュの理由でシングルトンになります)。サービスにリクエストを行うブラウザ クライアントは、次の 3 つの結果のいずれかを取得できます。

1) キャッシュにデータがあり、データの有効期限が切れていない (古い) - このデータを返します。とても早い。2) キャッシュされたデータの有効期限が切れていますが、別の要求が既にデータベースを照会しています。キャッシュされたデータを返しました。3) キャッシュされたデータの有効期限が切れており、DB に対してクエリを実行するリクエストがありません。この要求は、クエリを作成するために前進します。

ただし、同じ名前のストアド プロシージャを対象とするデータベース クエリは、キューに入れる必要があります (要件)。

したがって、これらのクエリをキューに入れ、同時にではなく連続して実行するように設計されたこのキュー クラスを作成しました。これらのキュー クラスは必要に応じて作成され、シングルトン クラスのリストに格納されます。要求がパート (3) に移動すると、そのストアド プロシージャ名に一致するキュー クラスを見つけ、そのキュー クラスに要求を送信します。次に、DB からデータが返されるまで待機して、HTML 要求を処理できるようにします。

残念ながら、このコードを配置して数時間後、サーバー プロセスは 100% に達します。

マルチスレッドコーディングは私の専門ではないため、改善する最善の方法はわかりません。

キュー クラスのコードは次のようになります。

public ReportTable GetReportTable(ReportQuery query)
{
  lock (_queue)
  {
    _queue.Enqueue(query);
    Monitor.Pulse(_queue);
  }

  lock (_queue)
  {
    var firstQueryInQueue = _queue.Peek();
    while (_inUse || firstQueryInQueue == null || firstQueryInQueue.GetHashCode() != query.GetHashCode())
    {
      Monitor.Pulse(_queue);
      Monitor.Wait(_queue);
    }

    _inUse = true;
    firstQueryInQueue = _queue.Dequeue();
    var table = firstQueryInQueue.GetNewReportTable();
    _inUse = false;

    Monitor.Pulse(_queue);
    return table;
  }
}
4

2 に答える 2

0

それで、これを修正するために私がしたことは次のとおりです。

public ReportTable GetReportTable(ReportQuery query)
{
  lock (_queue)
  {
    _queue.Enqueue(query);
    Monitor.Pulse(_queue);
  }

  lock (_queue)
  {
    var firstQueryInQueue = _queue.Peek();
    while (_inUse || firstQueryInQueue == null || firstQueryInQueue.GetHashCode() != query.GetHashCode())
    {
      Monitor.Wait(_queue);
    }

    _inUse = true;
    firstQueryInQueue = _queue.Dequeue();
    var table = firstQueryInQueue.GetNewReportTable();
    _inUse = false;

    Monitor.Pulse(_queue);
    return table;
  }
}

以前は機能しなかったのは、Monitor.Wait() と Monitor.Pulse() を完全に理解していなかったためです。コード内の間違った場所で Pulse() を使用していました。幸いなことに、 Wait() と Pulse() を非常によく説明する非常に優れた回答がここにあります。

重要なのは、コレクションが変更された後に Pulse() を実行し、後続のキューに入れられたスレッドに条件をテストする機会を与えることです。つまり、クエリがキューの最初にあるか? そして、他の誰かがすでにクエリを作成していますか? そのテストに失敗すると、スレッドは Wait() を呼び出します。これにより、スレッドは待機キューに入れられ、プロセッサ サイクルを拘束しません。先頭にありクエリを実行しているスレッドが完了すると、_inUse フラグを false に切り替えて Pulse() を呼び出し、次のスレッドの 1 つを起動して状態を確認できるようにします。

このソリューションを実装し、タスク マネージャーを 1 日観察した後、数時間サーバーに 1% から 5% の負荷がかかり、CPU が以前のように 100% に達することはありませんでした。

私はこれについて多くのことを読みましたが、このシナリオでは PulseAll() を呼び出すほうがよいようですが、これまでのところ Pulse() が機能しており、問題は発生していません。

于 2013-01-12T00:39:56.233 に答える
0

問題を理解しているかどうかわかりませんが、非常に簡単に書き直すことができます

private object _lockObj=new object();
public ReportTable GetReportTable(ReportQuery query)
{
  lock(_lockObj){
    var table = query.GetNewReportTable();
    return table;
  }
}
于 2013-01-11T21:05:20.327 に答える