Cập nhật - Trong khi câu trả lời này giải thích quy trình và cơ chế của không gian chạy PowerShell và cách chúng có thể giúp bạn khối lượng công việc không tuần tự đa luồng, đồng nghiệp PowerShell aficionado Warren 'Cookie Monster' F đã đi xa hơn và kết hợp các khái niệm tương tự này vào một công cụ duy nhất được gọi - nó thực hiện những gì tôi mô tả bên dưới, và anh ấy đã mở rộng nó với các công tắc tùy chọn để ghi nhật ký và trạng thái phiên chuẩn bị bao gồm các mô-đun nhập khẩu, công cụ thực sự tuyệt vời - Tôi thực sự khuyên bạn nên kiểm tra trước khi xây dựng giải pháp sáng bóng của riêng bạn!Invoke-Parallel
Với thực thi Parallel Runspace:
Giảm thời gian chờ đợi không thể bỏ qua
Trong trường hợp cụ thể ban đầu, lệnh được thực thi có một /nowait
tùy chọn ngăn chặn luồng gọi trong khi công việc (trong trường hợp này, đồng bộ hóa lại thời gian) tự kết thúc.
Điều này giúp giảm đáng kể thời gian thực hiện tổng thể từ góc độ nhà phát hành, nhưng việc kết nối với từng máy vẫn được thực hiện theo thứ tự. Việc kết nối với hàng ngàn khách hàng theo trình tự có thể mất nhiều thời gian tùy thuộc vào số lượng máy vì lý do này hay lý do khác không thể truy cập được, do sự tích lũy của thời gian chờ.
Để giải quyết việc phải xếp hàng tất cả các kết nối tiếp theo trong trường hợp một hoặc một vài lần hết thời gian liên tiếp, chúng ta có thể gửi công việc kết nối và gọi các lệnh để phân tách các không gian PowerShell, thực thi song song.
Runspace là gì?
Một Runspace là container ảo trong đó mã thực thi PowerShell của bạn, và đại diện / giữ môi trường từ quan điểm của một PowerShell tuyên bố / lệnh.
Theo nghĩa rộng, 1 Runspace = 1 luồng thực thi, vì vậy tất cả những gì chúng ta cần để "đa luồng" tập lệnh PowerShell của chúng ta là một tập hợp các Không gian chạy có thể lần lượt thực thi song song.
Giống như vấn đề ban đầu, công việc gọi các lệnh nhiều không gian có thể được chia thành:
- Tạo một RunspacePool
- Chỉ định tập lệnh PowerShell hoặc một đoạn mã thực thi tương đương cho RunspacePool
- Gọi mã không đồng bộ (nghĩa là không phải đợi mã trở lại)
Mẫu RunspacePool
PowerShell có một trình tăng tốc kiểu được gọi là [RunspaceFactory]
sẽ hỗ trợ chúng ta trong việc tạo ra các thành phần runspace - hãy để nó hoạt động
1. Tạo một RunspacePool và Open()
nó:
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()
Hai đối số truyền cho CreateRunspacePool()
, 1
và 8
là mức tối thiểu và số lượng tối đa runspaces phép thực hiện tại bất kỳ thời điểm nào, cho chúng ta một cách hiệu quả tối đa mức độ song song của 8.
2. Tạo một phiên bản của PowerShell, đính kèm một số mã thực thi vào nó và gán nó cho RunspacePool của chúng tôi:
Một phiên bản của PowerShell không giống như powershell.exe
quy trình (thực sự là một ứng dụng Máy chủ), nhưng là một đối tượng thời gian chạy bên trong đại diện cho mã PowerShell để thực thi. Chúng tôi có thể sử dụng trình [powershell]
tăng tốc loại để tạo phiên bản PowerShell mới trong PowerShell:
$Code = {
param($Credentials,$ComputerName)
$session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument("computer1.domain.tld")
$PSinstance.RunspacePool = $RunspacePool
3. Gọi đối tượng PowerShell không đồng bộ bằng APM:
Sử dụng thuật ngữ được biết đến trong thuật ngữ phát triển .NET làm Mô hình lập trình không đồng bộ , chúng ta có thể chia lời gọi của một lệnh thành một Begin
phương thức, để đưa ra "đèn xanh" để thực thi mã và một End
phương thức để thu thập kết quả. Vì trong trường hợp này chúng tôi không thực sự quan tâm đến bất kỳ phản hồi nào (chúng tôi không chờ đầu ra từ mọi cách w32tm
), chúng tôi có thể thực hiện bằng cách chỉ cần gọi phương thức đầu tiên
$PSinstance.BeginInvoke()
Gói nó trong RunspacePool
Sử dụng kỹ thuật trên, chúng ta có thể gói các vòng lặp tuần tự để tạo các kết nối mới và gọi lệnh từ xa trong luồng thực thi song song:
$ComputerNames = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName
$Code = {
param($Credentials,$ComputerName)
$session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$creds = Get-Credential domain\user
$rsPool = [runspacefactory]::CreateRunspacePool(1,8)
$rsPool.Open()
foreach($ComputerName in $ComputerNames)
{
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument($ComputerName)
$PSinstance.RunspacePool = $rsPool
$PSinstance.BeginInvoke()
}
Giả sử rằng CPU có khả năng thực thi tất cả 8 không gian chạy cùng một lúc, chúng ta sẽ có thể thấy rằng thời gian thực hiện được giảm đáng kể, nhưng với chi phí dễ đọc của tập lệnh do các phương thức khá "tiên tiến" được sử dụng.
Xác định mức độ tối ưu của thị sai:
Chúng tôi có thể dễ dàng tạo RunspacePool cho phép thực hiện 100 không gian chạy cùng một lúc:
[runspacefactory]::CreateRunspacePool(1,100)
Nhưng vào cuối ngày, tất cả chỉ ra có bao nhiêu đơn vị thực thi CPU cục bộ của chúng tôi có thể xử lý. Nói cách khác, miễn là mã của bạn đang thực thi, sẽ không có ý nghĩa khi cho phép nhiều không gian chạy hơn bạn có bộ xử lý logic để gửi thực thi mã đến.
Nhờ có WMI, ngưỡng này khá dễ xác định:
$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)
Mặt khác, nếu mã mà bạn đang thực thi phải chịu nhiều thời gian chờ đợi do các yếu tố bên ngoài như độ trễ mạng, bạn vẫn có thể hưởng lợi từ việc chạy nhiều không gian chạy hơn so với bộ xử lý logic, vì vậy bạn có thể muốn kiểm tra phạm vi tối đa có thể runspaces để tìm hòa vốn :
foreach($n in ($NumberOfLogicalProcessors..($NumberOfLogicalProcessors*3)))
{
Write-Host "$n: " -NoNewLine
(Measure-Command {
$Computers = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName -First 100
...
[runspacefactory]::CreateRunspacePool(1,$n)
...
}).TotalSeconds
}