こんにちは。明月です。
この投稿はC#のThreadPoolの使い方に関する説明です。
以前の投稿でThreadに関して説明しました。
link - [C#] 37. スレッド(Thread)を使い方、Thread.Sleep関数を使い方
Threadとはプログラム内で並列処理のための機能です。
でも、このThreadは個数を制御することができないと逆にプログラムの性能が落ちるということも説明しました。そのため、Thread個数を管理することも作成しなければならないですが、.Net frameworkにはこのThreadの個数を管理する機能があり、それがThreadPoolです。
using System;
using System.Threading;
namespace Example
{
// スレッドのパラメータクラス
class Node
{
// コンソールが出力する時に使うテキスト
public string Text { get; set; }
// 繰り返しの回数
public int Count { get; set; }
// Sleepの時間チック
public int Tick { get; set; }
}
class Program
{
// スレッド実行関数
static void ThreadProc(Object callBack)
{
// パラメータ値がNodeクラスタイプではなければ終了
if (callBack.GetType() != typeof(Node))
{
return;
}
// Nodeタイプで強制キャスト(データタイプがObjectタイプ)
var node = (Node)callBack;
// 設定された繰り返し回数ほど
for (int i = 0; i < node.Count; i++)
{
// コンソールが出力
Console.WriteLine(node.Text + " = " + i);
// 設定されたSleep時間チック
Thread.Sleep(node.Tick);
}
// 完了、コンソールが出力
Console.WriteLine("Complete " + node.Text);
}
// 実行関数
static void Main(string[] args)
{
// ThreadPoolの最小スレッド個数は0個、最大スレッド個数は2個で設定
// 参考、二つ目のパラメータは非同期I/Oスレッド個数(ただ、0で設定しても関係ない。)
if (ThreadPool.SetMinThreads(0, 0) && ThreadPool.SetMaxThreads(2, 0))
{
// ThreadPoolに登録、デリゲート関数でThreadProcを登録
// パラメータでNodeインスタンスを生成して渡す
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "A", Count = 3, Tick = 1000 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "B", Count = 5, Tick = 10 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "C", Count = 2, Tick = 500 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "D", Count = 7, Tick = 300 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "E", Count = 4, Tick = 200 });
}
// 任意のキーを押してください
Console.WriteLine("Press Any key...");
Console.ReadLine();
}
}
}
上の例をみれば私がThreadPoolクラスを利用してスレッドを総5個生成します。
でも、ThreadPoolには同時に実行するスレッド個数は0個から最大に2個まで生成されるように設定しました。つまり、Threadを無制限に生成して実行することではなく、Pool中で総量を設定してその以上は待機状況に待ちます。
つまり、結果を見ても、AとBは同時に実行されますが、CからはBが終了された状況で実行されることを確認でいます。Threadを最大に2個を実行しますが、その以上は実行されない設定でThreadPoolを管理します。
ThreadPoolにはThreadの生成を管理することになります。
でも、このThreadPoolはJoin関数がないので、Threadのすべてが終了する時まで待つ関数がありません。
using System;
using System.Threading;
namespace Example
{
// スレッドのパラメータクラス
class Node
{
// コンソールが出力する時に使うテキスト
public string Text { get; set; }
// 繰り返しの回数
public int Count { get; set; }
// Sleepの時間チック
public int Tick { get; set; }
}
class Program
{
// スレッド実行関数
static void ThreadProc(Object callBack)
{
// パラメータ値がNodeクラスタイプではなければ終了
if (callBack.GetType() != typeof(Node))
{
return;
}
// Nodeタイプで強制キャスト(データタイプがObjectタイプ)
var node = (Node)callBack;
// 設定された繰り返し回数ほど
for (int i = 0; i < node.Count; i++)
{
// コンソールが出力
Console.WriteLine(node.Text + " = " + i);
// 設定されたSleep時間チック
Thread.Sleep(node.Tick);
}
// 完了、コンソールが出力
Console.WriteLine("Complete " + node.Text);
}
// スレッドプールのJoin設定関数
static void ThreadPoolJoin(int size)
{
// 無限ループ
while (true)
{
// 1秒待機
Thread.Sleep(1000);
// 現在待機中のスレッド個数を取得するための変数
int count = 0;
int iocount = 0;
// 現在、使用可能なあまりスレッド個数を取得
ThreadPool.GetAvailableThreads(out count, out iocount);
// Max Threadと同じなら無限ループ終了
if (count == size)
{
// 無限ループ終了
break;
}
}
}
// 実行関数
static void Main(string[] args)
{
// ThreadPoolの最小スレッド個数は0個、最大スレッド個数は2個で設定
// 参考、二つ目のパラメータは非同期I/Oスレッド個数(ただ、0で設定しても関係ない。)
if (ThreadPool.SetMinThreads(0, 0) && ThreadPool.SetMaxThreads(2, 0))
{
// ThreadPoolに登録、デリゲート関数でThreadProcを登録
// パラメータでNodeインスタンスを生成して渡す
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "A", Count = 3, Tick = 100 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "B", Count = 5, Tick = 10 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "C", Count = 2, Tick = 500 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "D", Count = 7, Tick = 300 });
ThreadPool.QueueUserWorkItem(ThreadProc, new Node { Text = "E", Count = 4, Tick = 200 });
}
// 総2個のスレッドが待機状況になるまで待つ
ThreadPoolJoin(2);
// 任意のキーを押してください
Console.WriteLine("Press Any key...");
Console.ReadLine();
}
}
}
上の例はThreadPoolJoinという関数を作成してThreadPoolで使えるスレッド個数を確認してスレッドを使える最大量と同じなら無限ループが終了することでJoin関数を作成しました。
簡単なプログラムなら上みたいにThreadPoolを制御することができます。
問題は大きいプログラムならThreadPoolクラスの特性がstaticなのでどのところで使っているか確認することが難しいです。
ですから設計しているところでThreadPoolの使いが終了しました。と思っても、ライブラリや他のクラスでThreadPoolを使うと思えば、待機状況の個数でJoinをコントロールすると予想できないバグが発生する可能性があります。
それでTaskで使えるEventWaitHandleを利用すれば部分的にThreadPoolを制御することができます。
using System;
using System.Threading;
using System.Collections.Generic;
namespace Example
{
// スレッドのパラメータクラス
// EventWaitHandleクラスの継承
public class Node : EventWaitHandle
{
// コンストラクタ設定
public Node() : base(false, EventResetMode.ManualReset) { }
// コンソールが出力する時に使うテキスト
public string Text { get; set; }
// 繰り返しの回数
public int Count { get; set; }
// Sleepの時間チック
public int Tick { get; set; }
}
class Program
{
// スレッド実行関数
static void ThreadProc(Object callBack)
{
// パラメータ値がNodeクラスタイプではなければ終了
if (callBack.GetType() != typeof(Node))
{
// EventWaitHandleのJoin設定終了
node.Set();
return;
}
// Nodeタイプで強制キャスト(データタイプがObjectタイプ)
var node = (Node)callBack;
// 設定された繰り返し回数ほど
for (int i = 0; i < node.Count; i++)
{
// コンソールが出力
Console.WriteLine(node.Text + " = " + i);
// 設定されたSleep時間チック
Thread.Sleep(node.Tick);
}
// 完了、コンソールが出力
Console.WriteLine("Complete " + node.Text);
// EventWaitHandleのJoin設定終了
node.Set();
}
// Nodeクラスのインスタンスを追加した後、Listに登録
static EventWaitHandle AddNode(List<EventWaitHandle> list, string text, int count, int tick)
{
// インスタンス生成
var node = new Node { Text = text, Count = count, Tick = tick };
// listに登録
list.Add(node);
// インスタンスをリターン
return node;
}
// 実行関数
static void Main(string[] args)
{
// JoinのためのEventWaitHandleリスト
var list = new List<EventWaitHandle>();
// ThreadPoolの最小スレッド個数は0個、最大スレッド個数は2個で設定
// 参考、二つ目のパラメータは非同期I/Oスレッド個数(ただ、0で設定しても関係ない。)
if (ThreadPool.SetMinThreads(0, 0) && ThreadPool.SetMaxThreads(2, 0))
{
// ThreadPoolに登録、デリゲート関数でThreadProcを登録
// 関数AddNodeを呼び出してインスタンスを生成して上のlistに登録
ThreadPool.QueueUserWorkItem(ThreadProc, AddNode(list, "A", 3, 1000));
ThreadPool.QueueUserWorkItem(ThreadProc, AddNode(list, "B", 5, 10));
ThreadPool.QueueUserWorkItem(ThreadProc, AddNode(list, "C", 2, 500));
ThreadPool.QueueUserWorkItem(ThreadProc, AddNode(list, "D", 7, 300));
ThreadPool.QueueUserWorkItem(ThreadProc, AddNode(list, "E", 4, 200));
}
// listにあるEventWaitHandle関数がすべてSetが呼び出したらJoin解除
WaitHandle.WaitAll(list.ToArray());
// 任意のキーを押してください
Console.WriteLine("Press Any key...");
Console.ReadLine();
}
}
}
listをローカル変数に設定してWaitHandle.WaitAllから並列に変換すればThreadPoolが終了する時まで制御ができます。
ThreadPoolが全体に使っているスレッド個数を制御することですごく便利ですが、生成されたスレッドを待つ機能(Join)に関してはずいぶん大変ですね。
でも、性能のためにThreadをそのままに使うことよりThreadPoolを利用することがシステム性能のためにもっといい機能です。
参考でSetMinThreads関数とSetMaxThreads関数でスレッド個数の制御を設定しても、Threadで生成されるスレッドには影響がありません。つまり、Poolの設定を2個だけ設定してもThreadインスタンスは10個を設定することが可能です。
ここまでC#のThreadPoolの使い方に関する説明でした。
ご不明なところや間違いところがあればコメントしてください。
- [C#] 45. ネットワークソケット通信(Socket)を使い方2021/10/06 19:06:25
- [C#] 44. ファイル(FileInfo)とディレクトリ(DirectoryInfo)を扱い2021/10/05 19:29:34
- [C#] 43. ストリーム(Stream)とバイナリ(byte[])、エンコード(Encoding)、そしてusingを使い方とIDisposableインターフェース2021/10/04 18:33:04
- [C#] 42. ファイルを扱い(IO)とファイルメタデータ(FileInfo)を使い方2021/10/01 20:10:21
- [C#] 41. Taskクラスとasync、awaitを使い方2021/10/01 18:59:14
- [C#] 40. Linqを利用した並列処理(Parallel)を使い方2020/05/13 17:37:13
- [C#] 39. lockキーワードとdeadlock(デッドロック)2019/07/24 00:57:35
- [C#] 38. ThreadPoolの使い方2019/07/23 00:05:40
- [C#] 37. スレッド(Thread)を使い方、Thread.Sleep関数を使い方2019/07/22 23:45:05
- [C#] 36. 拡張メソッドを使い方2019/07/22 23:30:17
- [C#] 35. 文字列クラス、StringとStringBuilderを使い方2019/07/22 23:15:42
- [C#] 34. 最上位クラス(Object クラス)2019/07/20 02:27:23
- [C#] 33. 匿名形式(Anonymous Types)を使い方2019/07/20 02:22:03
- [C#] 32. ジェネリックタイプ(Generic Type)を使い方2019/07/18 22:50:16
- [C#] 31. アトリビュート(Attribute)を使い方2019/07/18 20:22:16
- check2024/04/10 19:03:53
- [Java] 64.Spring bootとReactを連結する方法(Buildする方法)2022/03/25 21:02:18
- [Javascript] Node.jsをインストールしてReactを使う方法2022/03/23 18:01:34
- [Java] 63. Spring bootでcronスケジューラとComponentアノテーション2022/03/16 18:57:30
- [Java] 62. Spring bootでWeb-Filterを設定する方法(Spring Security)2022/03/15 22:16:37
- [Java] JWT(Json Web Token)を発行、確認する方法2022/03/14 19:12:58
- [Java] 61. Spring bootでRedisデータベースを利用してセッションクラスタリング設定する方法2022/03/01 18:20:52
- [Java] 60. Spring bootでApacheの連結とロードバランシングを設定する方法2022/02/28 18:45:48
- [Java] 59. Spring bootのJPAでEntityManagerを使い方2022/02/25 18:27:48
- [Java] 58. EclipseでSpring bootのJPAを設定する方法2022/02/23 18:11:10
- [Java] 57. EclipseでSpring bootを設定する方法2022/02/22 19:04:49
- [Python] Redisデータベースに接続して使い方2022/02/21 18:23:49
- [Java] Redisデータベースを接続して使い方(Jedisライブラリ)2022/02/16 18:13:17
- [C#] Redisのデータベースを接続して使い方2022/02/15 18:46:09
- [CentOS] Redisデータベースをインストールする方法とコマンドを使い方2022/02/14 18:33:07