次の方法で共有


about_Thread_Jobs

簡単な説明

PowerShell スレッド ベースのジョブに関する情報を提供します。 スレッド ジョブは、現在のセッション プロセス内の別のスレッドでコマンドまたは式を実行するバックグラウンド ジョブの一種です。

詳細な説明

PowerShell は、ジョブを介してコマンドとスクリプトを同時に実行します。 同時実行をサポートするために、PowerShell によって提供されるジョブの種類は 3 つあります。

  • RemoteJob - コマンドとスクリプトはリモート セッションで実行されます。 詳細については、 about_Remote_Jobsを参照してください。
  • BackgroundJob - コマンドとスクリプトは、ローカル コンピューター上の別のプロセスで実行されます。 詳細については、「about_Jobs」を参照してください。
  • PSTaskJob または ThreadJob - コマンドとスクリプトは、ローカル コンピューター上の同じプロセス内の別のスレッドで実行されます。

スレッド ベースのジョブは、異なるスレッドで同じプロセスで実行されるため、リモート ジョブやバックグラウンド ジョブほど堅牢ではありません。 1 つのジョブでプロセスがクラッシュする重大なエラーが発生した場合、プロセス内の他のすべてのジョブが終了します。

ただし、スレッド ベースのジョブのオーバーヘッドは少なくなります。 リモート処理レイヤーやシリアル化は使用しません。 結果オブジェクトは、現在のセッションのライブ オブジェクトへの参照として返されます。 このオーバーヘッドがなければ、スレッドベースのジョブの実行速度が速くなり、他のジョブの種類よりも使用するリソースが少なくなります。

重要

また、ジョブを作成した親セッションは、ジョブの状態を監視し、パイプライン データを収集します。 ジョブの子プロセスは、ジョブが完了した状態に達すると、親プロセスによって終了されます。 親セッションが終了すると、実行中のすべての子ジョブが子プロセスと共に終了します。

この状況を回避するには、次の 2 つの方法があります。

  1. Invoke-Commandを使用して、切断されたセッションで実行されるジョブを作成します。 詳細については、「 about_Remote_Jobs」を参照してください。
  2. Start-Processを使用して、ジョブではなく新しいプロセスを作成します。 詳細については、「Start-Process」を参照してください。

スレッド ベースのジョブを開始および管理する方法

スレッド ベースのジョブを開始するには、次の 2 つの方法があります。

  • Start-ThreadJob - ThreadJob モジュールから
  • ForEach-Object -Parallel -AsJob - PowerShell 7.0 で並列機能が追加されました

スレッド ベースのジョブを管理するには、about_Jobsで説明されているのと同じ Job コマンドレットを使用します。

Start-ThreadJob の使用

ThreadJob モジュールは、最初に PowerShell 6 に付属しています。 Windows PowerShell 5.1 のPowerShell ギャラリーからインストールすることもできます。

ローカル コンピューターでスレッド ジョブを開始するには、コマンドまたはスクリプトを中かっこ ({ }) で囲んだ Start-ThreadJob コマンドレットを使用します。

次の例では、ローカル コンピューターで Get-Process コマンドを実行するスレッド ジョブを開始します。

Start-ThreadJob -ScriptBlock { Get-Process }

Start-ThreadJob コマンドは、実行中のジョブを表すThreadJob オブジェクトを返します。 ジョブ オブジェクトには、現在の実行中の状態を含む、ジョブに関する有用な情報が含まれています。 結果が生成されると、ジョブの結果が収集されます。

ForEach-Object -Parallel -AsJob の使用

PowerShell 7.0 では、 ForEach-Object コマンドレットに新しいパラメーター セットが追加されました。 新しいパラメーターを使用すると、PowerShell ジョブとして並列スレッドでスクリプト ブロックを実行できます。

データをパイプ処理して ForEach-Object -Parallelできます。 データは、並列で実行されるスクリプト ブロックに渡されます。 -AsJob パラメーターは、並列スレッドごとにジョブ オブジェクトを作成します。

次のコマンドは、コマンドにパイプされた各入力値の子ジョブを含むジョブを開始します。 各子ジョブは、パイプされた入力値を引数として使用して Write-Output コマンドを実行します。

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob

ForEach-Object -Parallel コマンドは、パイプされた各入力値の子ジョブを含むPSTaskJob オブジェクトを返します。 ジョブ オブジェクトには、状態を実行している子ジョブに関する有用な情報が含まれています。 結果が生成されると、子ジョブの結果が収集されます。

ジョブの完了を待機し、ジョブの結果を取得する方法

Wait-JobReceive-Jobなどの PowerShell ジョブ コマンドレットを使用して、ジョブが完了するのを待ってから、ジョブによって生成されたすべての結果を返すことができます。

次のコマンドは、 Get-Process コマンドを実行するスレッド ジョブを開始し、コマンドが完了するまで待機し、最後にコマンドによって生成されたすべてのデータ結果を返します。

Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job

次のコマンドは、パイプされた各入力に対して Write-Output コマンドを実行するジョブを開始してから、すべての子ジョブが完了するのを待機し、最後に子ジョブによって生成されたすべてのデータ結果を返します。

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

Receive-Job コマンドレットは、子ジョブの結果を返します。

1
3
2
4
5

各子ジョブは並列実行されるため、生成された結果の順序は保証されません。

スレッド ジョブのパフォーマンス

スレッド ジョブは、他の種類のジョブよりも高速で軽量です。 ただし、ジョブが実行している作業と比較すると、オーバーヘッドが大きくなる可能性があります。

PowerShell は、セッションでコマンドとスクリプトを実行します。 セッションで一度に実行できるコマンドまたはスクリプトは 1 つだけです。 そのため、複数のジョブを実行する場合、各ジョブは個別のセッションで実行されます。 各セッションはオーバーヘッドに寄与します。

スレッド ジョブは、実行する作業がジョブの実行に使用されるセッションのオーバーヘッドよりも大きい場合に最適なパフォーマンスを提供します。 この条件を満たすケースは 2 つあります。

  • 処理はコンピューティング集中型です。複数のスレッド ジョブでスクリプトを実行すると、複数のプロセッサ コアを利用して、より高速に完了できます。

  • 作業は重要な待機で構成されます。I/O またはリモート呼び出しの結果を待機する時間を費やすスクリプトです。 通常、並列実行は、順番に実行する場合よりも速く完了します。

(Measure-Command {
    1..1000 | ForEach { Start-ThreadJob { Write-Output "Hello $using:_" } } | Receive-Job -Wait
}).TotalMilliseconds
36860.8226

(Measure-Command {
    1..1000 | ForEach-Object { "Hello: $_" }
}).TotalMilliseconds
7.1975

上の最初の例は、単純な文字列書き込みを行うために 1000 個のスレッド ジョブを作成する foreach ループを示しています。 ジョブのオーバーヘッドにより、完了までに 36 秒以上かかります。

2 番目の例では、同じ 1000 操作を実行する ForEach コマンドレットを実行します。 今回 ForEach-Object は、ジョブのオーバーヘッドを発生させずに、1 つのスレッドで順番に実行されます。 わずか 7 ミリ秒で完了します。

次の例では、10 個の個別のシステム ログに対して最大 5,000 個のエントリが収集されます。 スクリプトには多数のログの読み取りが含まれるため、操作を並列で行うのが理にかなっています。

$logNames.count
10

Measure-Command {
    $logs = $logNames | ForEach-Object {
        Get-WinEvent -LogName $_ -MaxEvents 5000 2>$null
    }
}

TotalMilliseconds : 252398.4321 (4 minutes 12 seconds)
$logs.Count
50000

このスクリプトは、ジョブが並列で実行されるときに半分の時間で完了します。

Measure-Command {
    $logs = $logNames | ForEach {
        Start-ThreadJob {
            Get-WinEvent -LogName $using:_ -MaxEvents 5000 2>$null
        } -ThrottleLimit 10
    } | Wait-Job | Receive-Job
}

TotalMilliseconds : 115994.3 (1 minute 56 seconds)
$logs.Count
50000

スレッド ジョブと変数

スレッド ベースのジョブに値を渡す方法は複数あります。

Start-ThreadJob は、コマンドレットにパイプされた変数、 $using キーワードを使用してスクリプト ブロックに渡される変数、または ArgumentList パラメーターを介して渡される変数を受け入れることができます。

$msg = "Hello"

$msg | Start-ThreadJob { $input | Write-Output } | Wait-Job | Receive-Job

Start-ThreadJob { Write-Output $using:msg } | Wait-Job | Receive-Job

Start-ThreadJob { param ([string] $message) Write-Output $message } -ArgumentList @($msg) |
  Wait-Job | Receive-Job

ForEach-Object -Parallel は、パイプされた変数を受け取り、 $using キーワードを使用してスクリプト ブロックに直接渡される変数を受け取ります。

$msg = "Hello"

$msg | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

1..1 | ForEach-Object -Parallel { Write-Output $using:msg } -AsJob | Wait-Job | Receive-Job

スレッド ジョブは同じプロセスで実行されるため、ジョブに渡される変数参照型は慎重に処理する必要があります。 スレッド セーフ オブジェクトでない場合は、割り当てないようにし、メソッドとプロパティを呼び出すべきではありません。

次の例では、スレッド セーフな .NET ConcurrentDictionary オブジェクトをすべての子ジョブに渡して、一意の名前のプロセス オブジェクトを収集します。 スレッド セーフ オブジェクトであるため、ジョブがプロセス内で同時に実行されている間は安全に使用できます。

$threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
$jobs = Get-Process | ForEach {
    Start-ThreadJob {
        $proc = $using:_
        $dict = $using:threadSafeDictionary
        $dict.TryAdd($proc.ProcessName, $proc)
    }
}
$jobs | Wait-Job | Receive-Job

$threadSafeDictionary.Count
96

$threadSafeDictionary["pwsh"]

NPM(K)  PM(M)   WS(M) CPU(s)    Id SI ProcessName
------  -----   ----- ------    -- -- -----------
  112  108.25  124.43  69.75 16272  1 pwsh

関連項目