none
スレッドプールを用途ごとに分ける方法 RRS feed

  • 質問

  • スレッドプールを用途ごとに分けるにはどうすればよいでしょうか?

    何も考えずにtaskを複数生成すると全ていっしょくたに一つのスレッドプールが使われてしまうため、

    長時間(数秒)要するtaskが増えてくると、短時間で終わるはずのタスクも開始が遅れたり、完了までに時間がかかったりするようになってしまい困っています。

    スレッドプールではない専用スレッドを自身で作成するというのは避けたいです。あくまで単一のアプリケーションドメインでスレッドプールを使いたいです。

    数十個のtaskを常時並列で動かしています。長時間要するタスクのグループと短時間で済むタスクのグループでスレッドプールを分ける方法を教えてください。


    2019年5月13日 6:50

回答

  • 標準で目的・用途別にスレッドプールを分ける仕組みはありませんので、自作するしかありませんね。

    自分で Thread を作り、管理し、空いているスレッドに Task を分配するようなものを作れば良いです。
    (ThreadPoolTaskScheduler は QueueTask 時に ThreadPool に丸投げしているだけなので、自作スレッドプールの実装の参考にはなりません)

    TaskScheduler の実装例の1つは docs.microsoft.com で同時実行数を制限するサンプル が載っています。
    あとは同期オブジェクトを駆使するなど、スレッドプールを頑張って自作してください。
    (抱えているソースコードの特性次第では、前述のサンプル(同時実行数制限)をうまく使うだけでも良いのかもしれません)

    個人的には制御不能なぐらい Task を一気に詰む設計の時点で負け戦だと思っているので、その分、難易度が上がるのは避けられないのでは?と考えます。

    2019年5月14日 14:32
    モデレータ

すべての返信

  • Task.Runをお使いでしょうか? これとは別にTask.Factory.StartNewがあります。こちらを使うとTaskCreationOptionsが指定できますが、この中にTaskCreationOptions.LongRunningがあります。長時間実行するTaskについてはこのオプションを指定することをお勧めします。

    内部実装の話となりますが、このオプションが指定された場合、内部的にはThreadを使って実行しますので、ThreadPoolを消費しません。


    • 編集済み 佐祐理 2019年5月13日 7:21
    2019年5月13日 7:18
  • 毎秒複数のtaskを起動しており、そのたびにThreadを起動するのは起動に要する時間を無視できないため、起動済みのThreadを使い回す方式のThreadPoolを使いたいです。
    また、当方の使っている時間の要するasyncメソッド(DownloadStringTaskAsync)ではTaskCreationOptionを指定できません。

    具体的には、ThreadPoolをDownloadStringTaskAsyncするsiteごとに分けたいです。

    毎秒複数のsiteにDownloadStringTaskAsyncを実行しているのですが、ときどき特定のsiteでレスポンスが著しく遅くなることがあり、それが原因でthreadPool内のスレッドの起動が発生し、本来瞬時に完了するはずのsiteに対するDownloadStringTaskAsyncも開始がかなり遅延(数秒から数十秒)するというのが困っている問題です。

    ThreadPool.SetMaxThreads、ThreadPool.SetMinThreadsの数値を10〜1000の間でいろいろ試してみましたが問題は解決しません。


    • 編集済み kyounoii 2019年5月13日 8:26
    2019年5月13日 7:56
  • ThreadPoolを分ける仕組みも同じくTaskFactoryですので、クラスライブラリで暗黙的に作られるTaskについてはコントロールできない(or 困難)だと思います。

    他のアプローチについてコメントします。いくつか確認ですが、動作中のTask・スレッドの個数などは見積れていますでしょうか? また、アプリケーションは64bitで動作していますでしょうか?(32bit優先により32bit動作だったりしますでしょうか?)

    というのもマネージド スレッド プールには

    スレッド プールのキューに登録できる操作の数は、使用可能なメモリによってのみ制限されます。

    と説明されていますが、ほぼ仮想メモリで決まります。具体的には、ThreadPoolといえど最終的にはスレッドです。そして各スレッドには固有のスタックメモリが割り当てられています。そしてスタックメモリの既定値は1MBとなっています。そのため、32bitプロセスですと最大でも2000スレッドとなっています。既定のスタックサイズを変更するにはEXEファイルのヘッダー部分を操作する必要があります。それよりは手っ取り早く64bit動作させるのが楽です。

    これとは別にご存知かもしれませんが、HTTP仕様ではサイト毎の接続数が2と定められており、クラスライブラリではServicePointManager.DefaultConnectionLimitでコントロールしています。最近はHTTP仕様があまり守られていないこともあるため、値を変更するというのも一つの手です(自己責任でお願いします)。

    2019年5月13日 10:12
  • アプリケーションは64bitで動作しています。(visual studioのビルドタブの対象プラットフォームで確認)
    DefaultConnectionLimitは50000に設定しています。
    それでもDownloadStringTaskAsyncで時間を要するTaskがいくつか出てくると、他のTaskの開始が遅延してしまいます。

    >ThreadPoolを分ける仕組みも同じくTaskFactory
    TaskFactoryのコンストラクタに指定するカスタムTaskSchedulerを自身で作成するということでしょうか?
    その場合、ThreadPoolTaskSchedulerと同等のTaskSchedulerを作るにはどうすればよいでしょうか?
    ThreadPoolTaskSchedulerはinternal sealed classのため触れません。


    2019年5月14日 14:20
  • 標準で目的・用途別にスレッドプールを分ける仕組みはありませんので、自作するしかありませんね。

    自分で Thread を作り、管理し、空いているスレッドに Task を分配するようなものを作れば良いです。
    (ThreadPoolTaskScheduler は QueueTask 時に ThreadPool に丸投げしているだけなので、自作スレッドプールの実装の参考にはなりません)

    TaskScheduler の実装例の1つは docs.microsoft.com で同時実行数を制限するサンプル が載っています。
    あとは同期オブジェクトを駆使するなど、スレッドプールを頑張って自作してください。
    (抱えているソースコードの特性次第では、前述のサンプル(同時実行数制限)をうまく使うだけでも良いのかもしれません)

    個人的には制御不能なぐらい Task を一気に詰む設計の時点で負け戦だと思っているので、その分、難易度が上がるのは避けられないのでは?と考えます。

    2019年5月14日 14:32
    モデレータ
  • DefaultConnectionLimitは50000に設定しています。

    TCP/IPのポートは仕様上最大65535個しかなく、それ以上に並列動作させるためには別のIPアドレスを使う必要があります。

    それはそうと、日本においてはWebサイト管理者の意図しないアクセス方法をした場合に逮捕される事件が発生しています。DefaultConnectionLimitを50000に設定するということはRFCを明らかに逸脱していますし、DoS攻撃の意図があったとみなされるのが自然です。DefaultConnectionLimitは2に戻されることを強く推奨します。

    また私としては逮捕されたくないので、これ以上、回答には関われません。

    2019年5月14日 21:32
  • 本来一瞬で完了するはずのタスクの開始が遅延したり、完了までに時間を要してしまう問題の対策として、siteごとにThreadPoolを分けるというアイデアを挙げましたが、TaskSchedulerの自作は難しそうなので他の解決方法をご提案いただけないでしょうか?

    DownloadStringTaskAsyncのレスポンスが遅いということは、その間Threadが占有されているわけで、その分起動するThreadが増えてしまい、パンクするというのはC10K問題と同じことがクライアント側で発生しているように見えます。

    2019年5月15日 0:47
  • DefaultConnectionLimitはテストのため一時的に50000にしました。テストはローカルPCのWebサーバーに対して行なっています。現在は2に戻しました。

    Dos攻撃と誤認される可能性のあるデリケートな質問にもかかわらず、その点を配慮した文章にできていなかった私の不備を認めます。この問い合わせチケットはクローズします。


    • 編集済み kyounoii 2019年5月15日 6:42
    2019年5月15日 6:23