Powershell สามารถเรียกใช้คำสั่งแบบขนานได้หรือไม่


126

ฉันมีสคริปต์ powershell เพื่อทำการประมวลผลแบบแบตช์กับรูปภาพจำนวนมากและฉันต้องการทำการประมวลผลแบบขนาน ดูเหมือนว่า Powershell จะมีตัวเลือกการประมวลผลเบื้องหลังเช่น start-job, wait-job ฯลฯ แต่ทรัพยากรที่ดีเพียงอย่างเดียวที่ฉันพบสำหรับการทำงานคู่ขนานคือการเขียนข้อความของสคริปต์และเรียกใช้สิ่งเหล่านั้น ( PowerShell Multithreading )

ตามหลักการแล้วฉันต้องการบางสิ่งที่คล้ายกับ foreach แบบขนานใน. net 4

สิ่งที่ดูเหมือนจะไม่สวยเช่น:

foreach-parallel -threads 4 ($file in (Get-ChildItem $dir))
{
   .. Do Work
}

บางทีฉันอาจจะดีกว่าแค่ทิ้งตัวลงไปที่ c # ...


tl; dr: receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $aหรือ$a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $aโปรดทราบด้วยว่าหากคุณโทรไปreceive-jobก่อนงานจะเสร็จคุณอาจไม่ได้อะไรเลย
Andrew

นอกจากนี้(get-job $a).jobstateinfo.state;
Andrew

คำตอบ:


100

คุณสามารถดำเนินงานคู่ขนานใน Powershell 2 ใช้งานพื้นหลัง ตรวจสอบStart-Jobและ cmdlet ของงานอื่น ๆ

# Loop through the server list
Get-Content "ServerList.txt" | %{

  # Define what each job does
  $ScriptBlock = {
    param($pipelinePassIn) 
    Test-Path "\\$pipelinePassIn\c`$\Something"
    Start-Sleep 60
  }

  # Execute the jobs in parallel
  Start-Job $ScriptBlock -ArgumentList $_
}

Get-Job

# Wait for it all to complete
While (Get-Job -State "Running")
{
  Start-Sleep 10
}

# Getting the information back from the jobs
Get-Job | Receive-Job

3
ดังนั้นฉันจึงลองทำตามคำแนะนำนี้หลายครั้ง แต่ดูเหมือนว่าตัวแปรของฉันจะขยายไม่ถูกต้อง หากต้องการใช้ตัวอย่างเดียวกันเมื่อบรรทัดนี้ดำเนินการ: Test-Path "\\$_\c$\Something"ฉันคาดหวังว่ามันจะขยาย$_เป็นรายการปัจจุบัน อย่างไรก็ตามมันไม่ได้ แต่จะส่งกลับค่าว่างเปล่า สิ่งนี้ดูเหมือนจะเกิดขึ้นจากภายในบล็อกสคริปต์เท่านั้น ถ้าฉันเขียนค่านั้นออกมาทันทีหลังจากความคิดเห็นแรกดูเหมือนว่าจะทำงานได้อย่างถูกต้อง
rjg

1
@likwid - ดูเหมือนคำถามแยกต่างหากสำหรับเว็บไซต์
Steve Townsend

ฉันจะดูผลลัพธ์ของงานที่กำลังทำงานอยู่เบื้องหลังได้อย่างไร
SimpleGuy

@SimpleGuy - ดูข้อมูลเกี่ยวกับการจับเอาต์พุตได้ที่นี่ - stackoverflow.com/questions/15605095/… - ดูเหมือนว่าคุณจะไม่สามารถดูสิ่งนี้ได้อย่างน่าเชื่อถือจนกว่างานเบื้องหลังจะเสร็จสิ้น
Steve Townsend

@SteveTownsend ขอบคุณ! เอาท์พุทการรับชมที่จริงแล้วไม่ค่อยดีนักบนหน้าจอ มาพร้อมกับความล่าช้าดังนั้นจึงไม่มีประโยชน์สำหรับฉัน ฉันเริ่มกระบวนการบนเทอร์มินัลใหม่ (เชลล์) แทนดังนั้นตอนนี้แต่ละกระบวนการทำงานบนเทอร์มินัลที่แตกต่างกันซึ่งทำให้มุมมองของความคืบหน้าดีขึ้นมากและสะอาดขึ้นมาก
SimpleGuy

98

คำตอบจาก Steve Townsend นั้นถูกต้องตามทฤษฎี แต่ไม่ใช่ในทางปฏิบัติตามที่ @likwid ชี้ไว้ รหัสที่แก้ไขของฉันคำนึงถึงอุปสรรคในบริบทงาน - ไม่มีสิ่งใดขวางกั้นโดยปริยาย! $_ตัวแปรอัตโนมัติสามารถใช้ในลูปได้ แต่ไม่สามารถใช้ได้โดยตรงภายในบล็อกสคริปต์เนื่องจากอยู่ในบริบทแยกต่างหากที่สร้างขึ้นโดยงาน

ในการส่งผ่านตัวแปรจากบริบทพาเรนต์ไปยังบริบทลูกให้ใช้-ArgumentListพารามิเตอร์ on Start-Jobเพื่อส่งและใช้paramภายในบล็อกสคริปต์เพื่อรับ

cls
# Send in two root directory names, one that exists and one that does not.
# Should then get a "True" and a "False" result out the end.
"temp", "foo" | %{

  $ScriptBlock = {
    # accept the loop variable across the job-context barrier
    param($name) 
    # Show the loop variable has made it through!
    Write-Host "[processing '$name' inside the job]"
    # Execute a command
    Test-Path "\$name"
    # Just wait for a bit...
    Start-Sleep 5
  }

  # Show the loop variable here is correct
  Write-Host "processing $_..."

  # pass the loop variable across the job-context barrier
  Start-Job $ScriptBlock -ArgumentList $_
}

# Wait for all to complete
While (Get-Job -State "Running") { Start-Sleep 2 }

# Display output from all jobs
Get-Job | Receive-Job

# Cleanup
Remove-Job *

(โดยทั่วไปฉันต้องการให้การอ้างอิงถึงเอกสาร PowerShell เป็นหลักฐานสนับสนุน แต่อนิจจาการค้นหาของฉันก็ไร้ผลหากคุณทราบว่าเอกสารการแยกบริบทอยู่ที่ใดให้โพสต์ความคิดเห็นที่นี่เพื่อแจ้งให้เราทราบ!)


ขอบคุณสำหรับคำตอบนี้ ฉันลองใช้โซลูชันของคุณแล้ว แต่ไม่สามารถทำงานได้เต็มที่ คุณสามารถดูคำถามของฉันได้ที่นี่: stackoverflow.com/questions/28509659/…
David พูดว่า Reinstate Monica

หรืออีกวิธีหนึ่งคือการเรียกใช้ไฟล์สคริปต์แยกต่างหากเป็นเรื่องง่าย เพียงแค่ใช้Start-Job -FilePath script.ps1 -ArgumentList $_
Chad Zawistowski

อีกทางเลือกหนึ่งคือการส่งผ่านเบื้องต้นของการสร้างสคริปต์โดยที่ไม่มีการดำเนินการใด ๆ นอกจากการขยายตัวแปรแล้วเรียกใช้สคริปต์ที่สร้างขึ้นพร้อมกัน ฉันมีเครื่องมือเล็กน้อยที่อาจปรับให้เข้ากับการสร้างสคริปต์แม้ว่าจะไม่ได้มีไว้เพื่อรองรับการสร้างสคริปต์ก็ตาม คุณสามารถดูได้ที่นี่
Walter Mitty

นี้ได้ผล แต่ฉันไม่สามารถรับสตรีมเอาต์พุตฟีดสดจาก ScriptBlock ได้ ผลลัพธ์จะถูกพิมพ์เมื่อ ScriptBlock ส่งคืนเท่านั้น
vothaison

8

http://gallery.technet.microsoft.com/scriptcenter/Invoke-Async-Allows-you-to-83b0c9f0

ฉันสร้าง invoke-async ซึ่งช่วยให้คุณสามารถรันบล็อกสคริปต์ / cmdlets / ฟังก์ชันหลายรายการในเวลาเดียวกัน สิ่งนี้ยอดเยี่ยมสำหรับงานขนาดเล็ก (การสแกนซับเน็ตหรือแบบสอบถาม wmi เทียบกับเครื่อง 100 เครื่อง) เนื่องจากค่าใช้จ่ายในการสร้างรันสเปซเทียบกับเวลาเริ่มต้นของการเริ่มงานนั้นค่อนข้างรุนแรง สามารถใช้งานได้เช่นนั้น

ด้วย scriptblock

$sb = [scriptblock] {param($system) gwmi win32_operatingsystem -ComputerName $system | select csname,caption} 

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $server -SetParam system  -ScriptBlock $sb

เพียง cmdlet / function

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $servers -SetParam computername -Params @{count=1} -Cmdlet Test-Connection -ThreadCount 50

8

วันนี้มีคำตอบมากมาย:

  1. งาน (หรืองานด้ายใน PS 6/7 หรือโมดูล)
  2. เริ่มต้นกระบวนการ
  3. ขั้นตอนการทำงาน
  4. powershell api กับ runspace อื่น
  5. เรียกใช้คำสั่งกับคอมพิวเตอร์หลายเครื่องซึ่งทั้งหมดสามารถเป็น localhost ได้ (ต้องเป็นผู้ดูแลระบบ)
  6. แท็บหลายเซสชัน (runspace) ใน ISE หรือแท็บ ISE แบบรีโมตพาวเวอร์เชลล์
  7. Powershell 7 มีforeach-object -parallelทางเลือกสำหรับ # 4

นี่คือขั้นตอนการทำงานที่มี foreach -parallel อย่างแท้จริง:

workflow work {
  foreach -parallel ($i in 1..3) { 
    sleep 5 
    "$i done" 
  }
}

work

3 done
1 done
2 done

หรือเวิร์กโฟลว์ที่มีบล็อกคู่ขนาน:

function sleepfor($time) { sleep $time; "sleepfor $time done"}

workflow work {
  parallel {
    sleepfor 3
    sleepfor 2
    sleepfor 1
  }
  'hi'
}

work 

sleepfor 1 done
sleepfor 2 done
sleepfor 3 done
hi

นี่คือ api ที่มีตัวอย่าง runspaces:

$a =  [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b =  [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c =  [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1,$r2,$r3 = ($a,$b,$c).begininvoke() # run in background
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3) # wait
($a,$b,$c).streams.error # check for errors
($a,$b,$c).dispose() # clean

a done
b done
c done

7

งานพื้นหลังมีราคาแพงในการตั้งค่าและไม่สามารถใช้ซ้ำได้ PowerShell MVP Oisin Grehan มีตัวอย่างที่ดีของ PowerShell multi-threading

(ไซต์ 10/25/2010 หยุดทำงาน แต่สามารถเข้าถึงได้ผ่าน Web Archive)

ฉันใช้สคริปต์ Oisin ที่ดัดแปลงเพื่อใช้ในรูทีนการโหลดข้อมูลที่นี่:

http://rsdd.codeplex.com/SourceControl/changeset/view/a6cd657ea2be#Invoke-RSDDThreaded.ps1


Link rot ได้กำหนดไว้สำหรับคำตอบนี้
ลูกา

4

ในการตอบคำตอบก่อนหน้านี้คุณสามารถใช้Wait-Jobเพื่อรอให้งานทั้งหมดเสร็จสมบูรณ์:

For ($i=1; $i -le 3; $i++) {
    $ScriptBlock = {
        Param (
            [string] [Parameter(Mandatory=$true)] $increment
        )

        Write-Host $increment
    }

    Start-Job $ScriptBlock -ArgumentList $i
}

Get-Job | Wait-Job | Receive-Job


0

หากคุณใช้ powershell ข้ามแพลตฟอร์มล่าสุด (ซึ่งคุณควร btw) https://github.com/powershell/powershell#get-powershellคุณสามารถเพิ่ม single &เพื่อเรียกใช้สคริปต์คู่ขนานได้ (ใช้;รันตามลำดับ)

ในกรณีของฉันฉันต้องเรียกใช้สคริปต์ 2 npm พร้อมกัน: npm run hotReload & npm run dev


คุณยังสามารถตั้งค่า npm เพื่อใช้powershellสำหรับสคริปต์ (โดยค่าเริ่มต้นจะใช้cmdบน windows)

เรียกใช้จากโฟลเดอร์รูทของโปรเจ็กต์: npm config set script-shell pwsh --userconfig ./.npmrc จากนั้นใช้คำสั่ง npm script เดียว:npm run start

"start":"npm run hotReload & npm run dev"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.