2

スレッドセーフなロガーを設計する必要があります。私のロガーには、ログに記録するテキストを単にキューに入れる Log() メソッドが必要です。また、ロガーはロックフリーである必要があります。これにより、ロガーをロックせずに他のスレッドがメッセージをログに記録できます。何らかの同期イベントを待機し、標準の .NET ログ (スレッドセーフではない) を使用してキューからすべてのメッセージをログに記録する必要があるワーカー スレッドを設計する必要があります。だから私が興味を持っているのは、ワーカースレッドとログ機能の同期です。以下は、私が設計したクラスのスケッチです。ここで Monitor.Wait/Pulse を使用するか、ワーカー スレッドを一時停止して再開するには他の手段を使用する必要があると思います。ロガーのジョブがないときに CPU サイクルを消費したくありません。

別の言い方をすれば、ロガーを使用する呼び出し元スレッドをブロックしないロガーを設計したいと考えています。私は高性能システムを持っています - そしてそれは要件です。

class MyLogger
{
  // This is a lockfree queue - threads can directly enqueue and dequeue
  private LockFreeQueue<String> _logQueue;
  // worker thread
  Thread _workerThread;
  bool _IsRunning = true;

 // this function is used by other threads to queue log messages
  public void Log(String text)
{
  _logQueue.Enqueue(text);
}

// this is worker thread function
private void ThreadRoutine()
{
 while(IsRunning)
 {
   // do something here
 }
}    
}
4

3 に答える 3

5

「ロックフリー」とは、スレッドが互いにブロックしないという意味ではありません。これは、非常に効率的ですが、非常にトリッキーなメカニズムによって相互にブロックしていることを意味します。非常に高いパフォーマンスのシナリオでのみ必要であり、専門家でさえ間違っています (多く)。

最善のアドバイス: 「ロックフリー」を忘れて、「スレッドセーフ」キューを使用してください。

このページの「ブロッキング キュー」をお勧めします。

ThreadRoutineクラス自体に (Consumer)を含めるかどうかは選択の問題です。

あなたの質問の2番目の部分では、「何らかの同期イベント」が正確に何であるかによって異なります。メソッド呼び出しを使用する場合は、ワンショット スレッドを開始させます。セマフォで待機したい場合は、 モニターとパルスを使用しないでください。ここでは信頼できません。AutoResetEvent/ManualResetEvent を使用します。
どのように表面化するかは、使用方法によって異なります。

基本的な材料は次のようになります。

class Logger
{
    private AutoResetEvent _waitEvent = new AutoResetEvent(false);
    private object _locker = new object();
    private bool _isRunning = true;    

    public void Log(string msg)
    {
       lock(_locker) { _queue.Enqueue(msg); }
    }

    public void FlushQueue()
    {
        _waitEvent.Set();
    }

    private void WorkerProc(object state)
    {
        while (_isRunning)
        {
            _waitEvent.WaitOne();
            // process queue, 
            // ***
            while(true)
            {
                string s = null;
                lock(_locker)
                {
                   if (_queue.IsEmpty) 
                      break;
                   s = _queue.Dequeu();
                }
                if (s != null)
                  // process s
            }
        } 
    }
}

議論の一部は、Queue (マークされた) を処理するときに何をすべきかのようです***。キューをロックしてすべてのアイテムを処理し、その間に新しいエントリの追加がブロックされる (より長い) か、エントリを 1 つずつロックして取得し、毎回 (非常に) 短時間だけロックすることができます。その最後のシナリオを追加しました。

要約: ロックフリーのソリューションではなく、ブロックフリーのソリューションが必要です。Block-Free は存在しません。可能な限りブロックしないものを選択する必要があります。mys サンプル (不完全) の最後の繰り返しは、エンキューとデキューの呼び出しだけをロックする方法を示しています。それで十分速いと思います。

于 2010-01-12T12:19:26.013 に答える
3

プロファイラーは、単純なlockステートメントを使用して大きなオーバーヘッドが発生していることを示しましたか? ロックフリー プログラミングを正しく行うのは非常に難しいため、本当に必要な場合は、信頼できるソースから既存のものを使用することをお勧めします。

于 2010-01-12T12:45:42.093 に答える
1

アトミック操作を行う場合、これをロックフリーにすることは難しくありません。単方向リストを取ります。必要なのはヘッドポインターだけです。

ログ機能:
1. ログ項目 (ログ文字列を持つノード) をローカルで準備します。
2. ローカル ノードの次のポインタをheadに設定します。
3. ATOMIC:ヘッドをローカル ノードの次のアドレスと比較し、等しい場合は、ヘッドをローカル ノードのアドレスに置き換えます。
4. 操作が失敗した場合は、手順 2 から繰り返します。それ以外の場合、アイテムは「キュー」にあります。

ワーカー: 1.ヘッドをローカルに
コピーします。 2. ATOMIC: headをローカルのものと比較し、等しい場合はheadを NULLに置き換えます。 3. 操作が失敗した場合は、ステップ 1 から繰り返します。 4. 成功した場合は、アイテムを処理します。これは現在ローカルであり、「キュー」の外にあります。


于 2010-01-12T14:10:53.937 に答える