2012-01-27 12 views
3

非同期プログラミングは、スレッド・エコノミーによるWebサーバーのスケーラビリティーを実現する方法であり、非ブロッキング・スレッドが非常に少なくても、多くのシミュレート・リクエストを処理できます。たとえば、Node.jsは、非同期操作を使用する単一のスレッドのみを使用してスケーラビリティを実現します。スレッド・スケジューリングによるスケーラビリティ:非同期操作とスレッド・プール上のマルチスレッド・プロデューサ/コンシューマ・キューとの比較

私は現在MongoDbというデータベースを使用していますが、まだ非同期操作をサポートしていない公式のC#ドライバです。したがって、単純なプロデューサ/コンシューマキューを使用してmongodbリクエストを処理してブロッキングスレッドの数を減らすことを検討しています。これは、スレッドプールスレッドがdb要求をキューに挿入し、他のタスクを続行させることによって行われます。キューには、実際のdb要求を処理する1つ以上の専用スレッドがあり、要求が結果とともに返されると、結果はスレッドプールスレッドに渡されます。

しかし、私は現在、スレッドプールがスレッド数に最大限の制限があるため、スレッドプール(TPLとC#4.0のタスク)を使用するときにキューを使用する必要があるのか​​疑問に思っています。この制限に達すると、要求はスレッドプールスレッドが使用可能になるまでキューに入れられます。あたかもスレッドプールがキューの機能を提供しているかのように、まるで自分のキューを使用することで何も得られないかのようなものですか?

もう1つのすばらしい「C#4.0 in a nutshell」ブック、ページからの次のコメントがあります。 928: "do notブロックルールの例外があります。データベースサーバーを呼び出すときにブロックするのが一般的です。他のスレッドが同じサーバーと競合している場合は、ブロックするのが一般的です。これは、並行性の高いシステムでは、何千もの同時クエリーで終わると、要求が処理できるよりも速くデータベースに当たっていることを意味します。スレッド経済は、あなたの心配の中で最も少ないものです」

他のサーバーへのリクエストなど、他のものでブロックするのと比べて、dbリクエストでブロックするのがなぜ良いのか分かりません。 DBアクセスを必要としないかもしれない他の要求を処理するためにスレッドが解放されるように、データベース要求をブロックしないほうがよいでしょうか。

要約:スレッドプールスレッドの最大数に依存することによって経済性を達成できますか、単純なプロデューサのコンシューマキューを作成する方が良いでしょう。なぜデータベースサーバーへの呼び出しをブロックするのが大丈夫ですか?

答えて

4

dbaseクエリでTPスレッドをブロックしても問題ありません。引用されたフレーズは、TPスレッドのすべてがそのようなクエリでブロックしている場合にのみ妥当であると規定しています。それを主張することはできませんが、むしろ人工的なようです。

スレッドプールマネージャーの主な仕事は、マシンに使用可能なコア数よりも個多くのスレッドが実行されないようにすることです。これはスレッディングが非効率的になるため、スレッド間のコンテキスト切り替えは非常に高価です。ただし、実行中のTPスレッドがブロックしていて実際の処理を行っていない場合は、うまく動作しません。 TPマネージャがスマートではなく、を知っていると、TPスレッドはブロックされており、ブロックする時間を予測できません。 dbaseエンジンだけがそれを推測し、それは伝えません。

TPマネージャはこれに対処する簡単なアルゴリズムを持っています。実行中のTPスレッドが妥当な時間内に完了しない場合は、別のTPスレッドを実行することができます。あなたは今より多くの cpuコアよりもアクティブなスレッドを持っています。 "合理的な時間"は、.NET TPマネージャの場合、0.5秒です。

これは必要に応じて、既存のものが間違えている限り、追加のスレッドを実行することができます。

実際にThreadPool.GetMaxThreads()スレッド数に達すると、非常に不健全です。これはマシンのコア数の250倍の膨大な数です。 4コアのマシンでは、最大で499秒間進まなかったスレッドを999回実行します。これらのスレッドは、スタックのためにアドレス空間のギガバイトのを消費します。あなたはの方法ですが、ここに来たら「ここに何か間違っています」ということを超えています。

この回答にはいくつかの簡単な数値があります。操作が0.5秒以上かかるようになったら、それを専用のスレッドで実行することを考える必要があります。半分の時間を焼くことは本当にブロックするだけで可能になるので、スレッドはTPスレッドよりもはるかに適切です。そして、スレッドセーフなキューを使用して操作要求を送ります。保留中の要求の数に上限を設定して、キューをフラッディングしないようにすることも重要です。ブロックすることによってプロデューサーを絞ります。もちろん、今から何が起こるかを忘れないでください。データベースは、年齢とともに決して速くなることはありません。

+0

ありがとうございました。 BlockingCollectionで一般的なキューの実装を次のように使用することを検討しています。 aarg、コメント内の文字数が少ないので、次の回答を見てください。 – ssn

+0

私は今から40分後に私自身の回答を投稿することができませんので、ハングアップしてください:-) – ssn

0

ここには、コールバックのスレッドプールを使用するキューがあり、提案されているように(まだテストされていない)キューの上限があります。私はこれが仕事をするだろうと思いますか、あるいはより良い、あるいはより単純な選択肢がありますか?

public class CallbackQueueItem<T> { 
    public Func<T> Func { get; set; } 
    public Action<object> Callback { get; set; } 
} 

public class CallbackQueue<T> { 
    private readonly BlockingCollection<CallbackQueueItem<T>> _items; 

    public CallbackQueue(int upperLimit) { 
     _items = new BlockingCollection<CallbackQueueItem<T>>(upperLimit);    
    } 
    private BlockingCollection<CallbackQueueItem<T>> Items { 
     get { return _items; } 
    } 
    public void Start() 
    { 
     Task.Factory.StartNew(() => { 
      while(!Items.IsCompleted) { 
       CallbackQueueItem<T> item; 
       try { 
        item = Items.Take(); 
       } 
       catch(InvalidOperationException) { 
        break; 
       } 
       if(item != null) { 
        var result = item.Func(); 
        Task.Factory.StartNew(item.Callback,result); 
       } 
      } 
     }); 
    } 

    public void Stop() { 
     Items.CompleteAdding(); 
    } 

    public void Push(CallbackQueueItem<T> item) { 
     Items.Add(item); 
    } 
} 
関連する問題