about_Thread_Jobs
Краткое описание
Предоставляет сведения о заданиях на основе потоков PowerShell. Задание потока — это тип фонового задания, которое выполняет команду или выражение в отдельном потоке в текущем сеансовом процессе.
Подробное описание
PowerShell параллельно выполняет команды и скрипты с помощью заданий. Существует три типа заданий, предоставляемых PowerShell для поддержки параллелизма.
RemoteJob
— Команды и скрипты выполняются в удаленном сеансе. Дополнительные сведения см. в разделе about_Remote_Jobs.BackgroundJob
— Команды и скрипты выполняются в отдельном процессе на локальном компьютере. См. дополнительные сведения о заданиях.PSTaskJob
илиThreadJob
— команды и скрипты выполняются в отдельном потоке в рамках одного процесса на локальном компьютере.
Задания на основе потоков не так надежны, как удаленные и фоновые задания, так как они выполняются в одном процессе в разных потоках. Если в одном задании возникает критическая ошибка, которая завершает работу процесса, все остальные задания в процессе завершаются.
Однако задания на основе потоков требуют меньше накладных расходов. Они не используют уровень удаленного взаимодействия или сериализацию. Результирующий объект возвращается в виде ссылок на динамические объекты в текущем сеансе. Без этих издержек потоковые задания выполняются быстрее и используют меньше ресурсов, чем задания других типов.
Важно!
Родительский сеанс, создавший задание, также отслеживает состояние задания и собирает данные конвейера. Дочерний процесс задания завершается родительским процессом, когда задание достигает завершенного состояния. Если родительский сеанс завершается, все выполняемые дочерние задания завершаются вместе с дочерними процессами.
Обойти эту ситуацию можно двумя способами:
- Используйте для
Invoke-Command
создания заданий, которые выполняются в отключенных сеансах. Дополнительные сведения см. в разделе about_Remote_Jobs. - Используйте
Start-Process
для создания нового процесса, а не задания. Дополнительные сведения см. в статье Start-Process.
Запуск заданий на основе потоков и управление ими
Существует два способа запуска заданий на основе потоков:
Start-ThreadJob
— из модуля ThreadJob ;ForEach-Object -Parallel -AsJob
— функция параллелизма добавлена в PowerShell 7.0.
Используйте те же командлеты заданий, которые описаны в about_Jobs для управления заданиями на основе потоков.
Использование Start-ThreadJob
Модуль ThreadJob впервые поставляется с PowerShell 6. Его также можно установить из коллекция PowerShell для Windows PowerShell 5.1.
Чтобы запустить задание потока на локальном компьютере, используйте 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
содержащий дочерние задания для каждого входного значения, переданного по конвейеру. Объект job содержит полезные сведения о состоянии выполнения дочерних заданий. Он собирает результаты дочерних заданий по мере создания результатов.
Ожидание завершения задания и получение результатов задания
Можно использовать командлеты заданий PowerShell, такие как Wait-Job
и Receive-Job
, чтобы дождаться завершения задания, а затем возвращать все результаты, созданные заданием.
Следующая команда запускает задание потока, которое выполняет 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 выполняет команды и скрипты в сеансе. Одновременно в сеансе может выполняться только одна команда или скрипт. Поэтому при выполнении нескольких заданий каждое задание выполняется в отдельном сеансе. Каждый сеанс вносит свой вклад в издержки.
Задания потоков обеспечивают наилучшую производительность, если выполняемая ими работа превышает издержки сеанса, используемого для выполнения задания. Существует два случая, когда это соответствует этому критерию.
Работа ресурсоемкая. Выполнение скрипта в нескольких заданиях потоков может использовать преимущества нескольких ядер процессора и выполняться быстрее.
Работа состоит из значительного ожидания— скрипт, который тратит время на ожидание ввода-вывода или результатов удаленного вызова. Выполнение параллельно обычно завершается быстрее, чем при последовательном выполнении.
(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
В первом примере выше показан цикл foreach, который создает 1000 заданий потока для простой записи строки. Из-за накладных расходов на выполнение заданий требуется более 36 секунд.
Во втором примере выполняется ForEach
командлет , чтобы выполнить те же 1000 операций.
На этот раз ForEach-Object
выполняется последовательно в одном потоке без дополнительных затрат на задания. Он завершается всего за 7 миллисекундах.
В следующем примере для 10 отдельных системных журналов собирается до 5000 записей. Так как скрипт включает чтение нескольких журналов, имеет смысл выполнять операции параллельно.
$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