대부분의 사람들은 어느 순간 기본 PowerShell 스크립트로 해결하기에는 너무 느리다는 문제에 부딪힐 것입니다. 이는 네트워크의 많은 컴퓨터에서 데이터를 수집하거나 한 번에 대량의 새로운 사용자를 Active Directory에 생성하는 경우일 수 있습니다. 이러한 경우에는 더 많은 처리 능력을 사용하면 코드를 더 빠르게 실행할 수 있습니다. PowerShell 멀티스레딩을 사용하여 이를 해결하는 방법을 알아보겠습니다!
기본 PowerShell 세션은 단일 스레드입니다. 한 번에 한 명령을 실행하고 완료되면 다음 명령으로 이동합니다. 이것은 모든 것을 반복 가능하게 유지하고 많은 리소스를 사용하지 않기 때문에 좋습니다. 하지만 수행 중인 작업이 서로 종속되지 않으며 CPU 리소스가 충분하다면, 이 경우 멀티스레딩을 고려할 시간입니다.
이 문서에서는 동일한 콘솔을 통해 관리되는 동시에 여러 데이터 스트림을 처리하기 위해 다양한 PowerShell 멀티스레딩 기술을 이해하고 사용하는 방법을 배우게 될 것입니다.
PowerShell 멀티스레딩 이해하기
멀티스레딩은 한 번에 한 개 이상의 명령을 실행하는 방법입니다. PowerShell은 일반적으로 단일 스레드를 사용하지만, 코드를 병렬화하기 위해 하나 이상의 스레드를 사용하는 다양한 방법이 있습니다.
멀티스레딩의 주요 이점은 코드의 실행 시간을 감소시키는 것입니다. 이 시간 감소는 더 높은 처리 능력 요구사항과 교환됩니다. 멀티스레딩을 사용하면 한 번에 많은 작업이 수행되므로 시스템 리소스가 더 필요합니다.
예를 들어, Active Directory에 새로운 사용자를 생성하려는 경우 어떻게 할까요? 이 예제에서는 한 번에 하나의 명령만 실행되기 때문에 멀티스레딩할 것이 없습니다. 그러나 1000개의 새로운 사용자를 생성하려는 경우 상황은 달라집니다.
멀티스레딩을 사용하지 않으면 New-ADUser
명령을 1000번 실행하여 모든 사용자를 생성해야 합니다. 새로운 사용자를 생성하는 데 약 3초가 걸린다고 가정해 보겠습니다. 1000명의 사용자를 생성하기 위해서는 약 1시간이 걸릴 것입니다. 하지만 100개의 스레드를 사용하여 각각 10개의 명령을 실행한다면, 약 50분이 아닌 1분 이내로 작업을 완료할 수 있습니다!
완벽한 확장성은 보장되지 않을 수 있다는 점을 유의하세요. 코드 내에서 항목을 생성하고 삭제하는 작업은 시간이 소요됩니다. 단일 스레드를 사용하는 경우 PowerShell은 코드를 실행하고 완료합니다. 그러나 다중 스레드를 사용하는 경우 콘솔을 실행하는 원래 스레드는 다른 스레드를 관리하는 데 사용됩니다. 어느 정도 되면 원래 스레드는 다른 스레드를 관리하는 데에만 전념하느라 최대한의 작업을 수행할 수 없을 것입니다.
PowerShell 멀티스레딩을 위한 사전 준비
이 문서에서는 실습을 통해 PowerShell 멀티스레딩이 어떻게 동작하는지 배우게 될 것입니다. 함께 따라하고 싶다면 아래에 필요한 몇 가지 사항과 사용되는 환경에 대한 몇 가지 세부 정보를 확인해주세요.
- Windows PowerShell 버전 3 이상 – 명시적으로 언급하지 않는 한, 모든 코드는 Windows PowerShell 버전 3 이상에서 작동합니다. 예제에는 Windows PowerShell 버전 5.1을 사용합니다.
- 추가 CPU와 메모리를 확보하세요 – PowerShell과 병렬 처리를 위해 최소한의 추가 CPU와 메모리가 필요합니다. 이를 확보하지 못하면 성능 향상을 볼 수 없을 수도 있습니다.
우선순위 #1: 코드 수정!
PowerShell 다중 스레딩을 사용하여 스크립트의 실행 속도를 높이기 전에 몇 가지 사전 작업을 완료해야 합니다. 첫 번째로 코드를 최적화해야 합니다.
코드를 빠르게 실행하기 위해 추가 리소스를 할당할 수는 있지만, 다중 스레딩은 많은 복잡성을 동반합니다. 다중 스레딩 이전에 코드를 빠르게 실행할 수 있는 방법이 있다면 먼저 수행해야 합니다.
병목 지점 식별
코드를 병렬 처리하기 위한 첫 번째 단계는 속도가 느려지는 원인을 찾는 것입니다. 코드가 느린 이유는 잘못된 논리나 불필요한 반복문일 수 있으며, 다중 스레딩 이전에 빠른 실행을 위해 일부 수정을 할 수 있습니다.
코드를 빠르게 실행할 수 있는 일반적인 방법 중 하나는 필터링을 왼쪽으로 이동시키는 것입니다. 데이터와 상호 작용하는 경우, 데이터 양을 줄이기 위해 필터링해야 하는 경우 가능한 한 일찍 수행해야 합니다. 아래는 svchost 프로세스의 CPU 사용량을 가져오는 코드 예시입니다.
아래 예제는 모든 실행 중인 프로세스를 읽은 다음 단일 프로세스(svchost)를 필터링하는 것을 보여줍니다. 그런 다음 CPU 속성을 선택하고 해당 값이 null이 아닌지 확인합니다.
위의 코드와 아래 예제를 비교해보세요. 아래 코드는 동일한 결과를 가지지만 구성이 다릅니다. 아래 코드는 더 간단하며 가능한 모든 로직을 파이프 심볼의 왼쪽으로 이동시킵니다. 이렇게하면 관심 없는 프로세스를 반환하지 않도록 Get-Process가 막히게 됩니다.
위의 두 줄을 실행하는 데 걸리는 시간 차이는 아래에 나와 있습니다. 이 코드를 한 번만 실행한다면 117ms의 차이는 눈에 띄지 않을 수 있지만, 수천 번 실행한다면 누적될 것입니다.

스레드 안전한 코드 사용
먼저 코드가 “스레드 안전”한지 확인하세요. “스레드 안전”이라는 용어는 한 스레드가 코드를 실행하는 동안 다른 스레드가 동시에 동일한 코드를 실행하고 충돌을 일으키지 않는지를 나타냅니다.
예를 들어, 두 개의 다른 스레드에서 동일한 파일에 쓰는 것은 스레드 안전하지 않습니다. 파일에 무엇을 먼저 추가할지 알 수 없기 때문입니다. 반면 파일에서 읽는 두 개의 스레드는 스레드 안전합니다. 파일이 변경되지 않기 때문에 두 스레드는 동일한 출력을 얻습니다.
스레드 안전하지 않은 PowerShell 멀티스레딩 코드의 문제는 일관된 결과를 얻을 수 없다는 것입니다. 때때로 충돌을 일으키지 않도록 스레드가 적절한 시간에 작동하기 때문에 문제가 잘 해결될 수 있습니다. 그러나 다른 경우에는 충돌이 발생하며 일관되지 않은 오류 때문에 문제 해결이 어려워집니다.
하나의 시간에 두 개 또는 세 개의 작업만 실행한다면 파일에 동시에 쓰는 경우도 있을 수 있습니다. 그러나 코드를 20개 또는 30개의 작업으로 확장하면 최소한 두 개의 작업이 동시에 쓰려고 시도하지 않는 확률이 크게 줄어듭니다.
PSJobs를 사용한 병렬 실행
스크립트를 멀티스레드로 만드는 가장 쉬운 방법 중 하나는 PSJobs를 사용하는 것입니다. PSJobs에는 Microsoft.PowerShell.Core 모듈에 내장된 cmdlet이 있습니다. Microsoft.PowerShell.Core 모듈은 PowerShell 3 버전 이후의 모든 버전에 포함되어 있습니다. 이 모듈의 명령을 사용하면 코드를 백그라운드에서 실행하면서 다른 코드를 전경에서 계속 실행할 수 있습니다. 아래에서 사용 가능한 모든 명령을 볼 수 있습니다.

작업 추적하기
모든 PSJobs는 11개의 상태 중 하나에 속합니다. 이러한 상태는 PowerShell이 작업을 관리하는 방법입니다.
아래에서 작업이 속할 수 있는 가장 일반적인 상태 목록을 찾을 수 있습니다.
- 완료됨 – 작업이 완료되었으며 출력 데이터를 가져오거나 작업을 제거할 수 있습니다.
- 실행 중 – 작업이 현재 실행 중이며 작업을 강제로 중지하지 않으면 출력을 가져올 수 없습니다.
- 차단됨 – 작업은 아직 실행 중이지만 호스트에서 진행하기 전에 정보를 입력해야 합니다.
- 실패 – 작업 실행 중에 종료 오류가 발생했습니다.
시작된 작업의 상태를 가져오려면 Get-Job
명령을 사용합니다. 이 명령은 작업의 모든 속성을 가져옵니다.
아래는 작업에 대한 출력 예시입니다. 여기에서 상태가 완료됨임을 볼 수 있습니다. 아래 예시는 Start-Job
명령을 사용하여 작업 내에서 Start-Sleep 5
코드를 실행하는 예시입니다. 해당 작업의 상태는 Get-Job
명령을 사용하여 반환됩니다.

작업 상태가 완료됨으로 반환되면 스크립트 블록의 코드가 실행되고 실행이 완료됨을 의미합니다. 또한 HasMoreData
속성이 False
인 것을 볼 수 있습니다. 이는 작업이 완료된 후에는 출력이 제공되지 않았음을 의미합니다.
아래는 작업을 설명하는 데 사용되는 다른 상태의 예시입니다. Command
열에서 어떤 작업이 완료되지 않은 것으로 보이는지, 예를 들어 abc
초 동안 대기하려고 시도한 작업은 실패한 작업이었습니다.

새 작업 생성
위에서 본 것처럼 Start-Job
명령을 사용하면 작업 내에서 코드를 실행하는 새 작업을 생성할 수 있습니다. 작업을 생성할 때는 작업에 사용되는 스크립트 블록을 제공합니다. 그런 다음 PSJob은 고유한 ID 번호를 가진 작업을 생성하고 작업을 실행시킵니다.
여기에서의 주요 이점은 사용하는 스크립트블록 실행보다 Start-Job
명령을 실행하는 데 걸리는 시간이 적다는 것입니다. 아래 이미지에서 볼 수 있듯이 명령이 완료되기까지 5초가 걸리는 대신 작업을 시작하는 데는 0.15초만 걸렸습니다.

동일한 코드를 분수의 시간 안에 실행할 수 있었던 이유는 PSJob으로 백그라운드에서 실행되기 때문입니다. 코드를 백그라운드에서 설정하고 실행하기 시작하는 데 0.15초가 걸렸으며, 실제로 5초 동안 대기하는 것이 아니라 전경에서 실행하는 것과 달리 코드를 실행하는 데 걸리는 시간이었습니다.
작업 출력 검색
가끔 작업 내부의 코드가 출력을 반환합니다. Receive-Job
명령을 사용하여 해당 코드의 출력을 검색할 수 있습니다. Receive-Job
명령은 PSJob을 입력으로 받고 작업의 출력을 콘솔에 기록합니다. 작업이 실행되는 동안 출력된 내용은 작업을 검색할 때 저장되어 그 시점에서 저장된 모든 내용을 출력합니다.
아래 코드를 실행하는 예제입니다. 이는 Hello World를 출력하는 작업을 생성하고 시작합니다. 그런 다음 작업에서 출력을 검색하여 콘솔에 출력합니다.

예약된 작업 생성
PSJobs와 상호 작용하는 또 다른 방법은 예약 작업을 통해 가능합니다. 예약 작업은 작업 스케줄러와 유사한 Windows 예약 작업으로 구성될 수 있습니다. 예약 작업을 사용하면 예약된 작업에 복잡한 PowerShell 스크립트 블록을 쉽게 예약할 수 있습니다. 예약 작업을 사용하면 트리거를 기반으로 PSJob을 백그라운드에서 실행할 수 있습니다.
작업 트리거
작업 트리거는 특정 시간, 사용자 로그인 시간, 시스템 부팅 시간 등과 같은 것들을 의미할 수 있습니다. 또한 트리거를 일정한 간격으로 반복할 수도 있습니다. 이러한 트리거는 New-JobTrigger
명령을 사용하여 정의됩니다. 이 명령은 예약된 작업을 실행할 트리거를 지정하는 데 사용됩니다. 트리거가 없는 예약 작업은 수동으로 실행해야 하지만 각 작업에는 여러 개의 트리거를 가질 수 있습니다.
트리거가 있는 경우에도 일반 PSJob과 동일한 스크립트 블록이 있어야 합니다. 트리거와 스크립트 블록을 모두 갖게 되면 다음 섹션에 표시된대로 작업을 생성하기 위해 Register-ScheduledJob
명령을 사용합니다. 이 명령은 New-JobTrigger
명령으로 생성된 트리거와 실행될 스크립트 블록과 같은 예약된 작업의 속성을 지정하는 데 사용됩니다.
데모
어떤 사람이 컴퓨터에 로그인할 때마다 PowerShell 코드를 실행해야 할 수도 있습니다. 이를 위해 예약 작업을 생성할 수 있습니다.
이를 수행하려면 먼저 New-JobTrigger
를 사용하여 트리거를 정의하고 아래에 표시된 것과 같이 예약된 작업을 정의해야 합니다. 이 예약된 작업은 누군가 로그인할 때마다 로그 파일에 한 줄을 작성합니다.
위의 명령을 실행하면 작업 ID, 스크립트 블록 및 기타 속성이 표시되는 새 작업을 만드는 것과 유사한 출력이 생성됩니다.

일부 로그인 시도 후에 아래 스크린샷에서 확인할 수 있듯이 시도 내역이 기록되었습니다.

AsJob
매개변수 활용하기
작업을 사용하는 또 다른 방법은 많은 PowerShell 명령에 내장된 AsJob
매개변수를 사용하는 것입니다. 다양한 명령이 있으므로 아래에 표시된 대로 Get-Command
를 사용하여 모든 명령을 찾을 수 있습니다.
가장 일반적인 명령 중 하나는 Invoke-Command
입니다. 일반적으로 이 명령을 실행하면 명령이 즉시 실행됩니다. 일부 명령은 즉시 반환되어 계속 진행할 수 있지만, 일부 명령은 명령이 완료될 때까지 기다립니다.
AsJob
매개변수를 사용하면 실행된 명령을 콘솔에서 동기적으로 실행하는 대신 작업으로 실행합니다.
대부분의 경우 AsJob
은 로컬 컴퓨터에서 사용할 수 있지만, Invoke-Command
는 로컬 컴퓨터에서 실행하는 기본 옵션이 없습니다. ComputerName 매개변수 값을 Localhost로 사용하여 이 문제를 해결할 수 있습니다. 아래는 이 해결책의 예시입니다.
AsJob
매개변수를 보여주기 위해 아래 예제에서는 Invoke-Command
를 사용하여 5초 동안 대기한 다음 동일한 명령을 AsJob
을 사용하여 반복하여 실행 시간의 차이를 보여줍니다.

Runspaces: 작업보다 빠른 작업!
지금까지 내장된 명령어만을 사용하여 PowerShell에서 추가 스레드를 사용하는 방법에 대해 배웠습니다. 스크립트를 멀티스레드로 만들기 위한 또 다른 옵션은 별도의 runspace를 사용하는 것입니다.
Runspaces는 PowerShell을 실행하는 스레드(들)이 작동하는 동안의 공간입니다. PowerShell 콘솔에서 사용되는 runspace는 단일 스레드로 제한되지만, 추가 runspace를 사용하여 추가 스레드를 사용할 수 있습니다.
Runspace vs PSJobs
Runspace와 PSJob은 많은 유사점을 공유하지만, 성능 측면에서 큰 차이점이 있습니다. runspace와 PSJob의 가장 큰 차이점은 각각을 설정하고 해체하는 데 걸리는 시간입니다.
이전 섹션의 예제에서 생성된 PSJob은 약 150ms가 소요되었습니다. 이는 작업에 포함된 스크립트 블록에 거의 코드가 포함되지 않았으며 추가 변수가 작업에 전달되지 않았기 때문에 최상의 경우입니다.
PSJob 생성과 대조적으로, runspace는 미리 생성됩니다. runspace 작업을 시작하기 전에 대부분의 시간이 처리됩니다.
아래는 PSJob 대신 runspace에서 사용한 동일한 명령을 실행하는 예제입니다.

대조적으로, 아래는 runspace 버전에 사용되는 코드입니다. 동일한 작업을 실행하기 위해 훨씬 더 많은 코드가 있는 것을 볼 수 있습니다. 그러나 추가 코드의 이점은 거의 3/4의 시간을 절약하여 명령을 36ms 대신 148ms에서 실행할 수 있게 합니다.

Runspaces 실행: 안내서
처음에는 runspaces를 사용하는 것이 어려울 수 있습니다. PowerShell 명령의 안내 없이 .NET 클래스와 직접적으로 상호 작용해야합니다. 이 섹션에서는 PowerShell에서 runspace를 생성하는 데 필요한 내용을 살펴보겠습니다.
이 안내서에서는 PowerShell 콘솔과 별도의 runspace 및 별도의 PowerShell 인스턴스를 생성합니다. 그런 다음 새로운 runspace를 새로운 PowerShell 인스턴스에 할당하고 해당 인스턴스에 코드를 추가합니다.
Runspace 생성
먼저 새로운 runspace를 생성해야합니다. 이를 위해 runspacefactory
클래스를 사용합니다. 아래에 표시된대로 변수에 저장하여 나중에 참조할 수 있도록합니다.
runspace가 생성되었으므로 PowerShell 코드를 실행하기 위해 PowerShell 인스턴스에 할당합니다. 이를 위해 runspace와 유사하게 powershell
클래스를 사용하고 아래에 표시된대로 변수에 저장해야합니다.
다음으로, runspace를 PowerShell 인스턴스에 추가하고 코드를 실행할 수 있도록 runspace를 엽니다. 아래에는 5초 동안 대기하는 scriptblock이 포함되어 있습니다.
Runspace 실행
지금까지 스크립트 블록은 실행되지 않았습니다. 지금까지는 실행 공간에 모든 것을 정의하는 것만 수행되었습니다. 스크립트 블록을 실행하기 위해 두 가지 옵션이 있습니다.
- Invoke() –
Invoke()
메서드는 스크립트 블록을 실행 공간에서 실행하지만 실행 공간이 반환될 때까지 콘솔로 돌아가지 않습니다. 코드가 제대로 실행되는지 확인하기 위해 코드를 해제하기 전에 테스트하는 데 유용합니다. - BeginInvoke() –
BeginInvoke()
메서드를 사용하면 실제로 성능 향상을 볼 수 있습니다. 이렇게 하면 스크립트 블록이 실행 공간에서 실행을 시작하고 즉시 콘솔로 돌아갑니다.
BeginInvoke()
을 사용할 때는 출력을 변수에 저장해야 하며, 이 변수를 사용하여 실행 공간에서 스크립트 블록의 상태를 확인해야 합니다(아래 예시 참조).
BeginInvoke()
의 출력을 변수에 저장한 후, 작업의 상태를 확인하기 위해 해당 변수를 검사할 수 있습니다. IsCompleted
속성 참조.

또 다른 이유는 Invoke()
메서드와 달리, BeginInvoke()
는 코드가 완료된 후 자동으로 출력을 반환하지 않는다는 것입니다. 이를 위해 완료된 후에 EndInvoke()
메서드를 사용해야 합니다.
이 예제에서는 출력이 없지만, invoke를 종료하려면 아래 명령을 사용하면 됩니다.
모든 작업이 실행 공간에 대기열에 들어간 후에는 항상 실행 공간을 닫아야합니다. 이렇게하면 PowerShell의 자동 쓰레기 수집 프로세스가 사용되지 않은 리소스를 정리 할 수 있습니다. 아래는이 작업을 수행하는 데 사용되는 명령입니다.
실행 공간 풀 사용
실행 공간 사용은 성능을 향상시키지만 단일 스레드의 주요 제한 사항을 만납니다. 이는 실행 공간 풀이 여러 스레드를 사용하는 방식에서 빛을 발합니다.
이전 섹션에서는 두 개의 실행 공간 만 사용했습니다. PowerShell 콘솔 자체와 수동으로 생성한 실행 공간을 하나씩만 사용했습니다. 실행 공간 풀을 사용하면 하나의 변수를 사용하여 여러 실행 공간을 백그라운드에서 관리 할 수 있습니다.
이러한 다중 실행 공간 동작은 여러 실행 공간 개체로 수행 할 수 있지만, 실행 공간 풀을 사용하면 관리가 훨씬 쉬워집니다.
실행 공간 풀은 단일 실행 공간과 설정 방법이 다릅니다. 주요 차이점 중 하나는 실행 공간 풀에 사용할 수있는 최대 스레드 수를 정의한다는 것입니다. 단일 실행 공간의 경우 단일 스레드로 제한되지만 풀을 사용하면 풀이 확장 할 수있는 최대 스레드 수를 지정합니다.
실행 공간 풀에서 권장하는 스레드 수는 수행되는 작업 수 및 코드를 실행하는 기계에 따라 다릅니다. 대부분의 경우 최대 스레드 수를 늘리는 것은 속도에 부정적인 영향을 미치지 않지만 혜택도 볼 수 없을 수도 있습니다.
실행 공간 풀 속도 데모
단일 runspace보다 runspace 풀이 우세한 예시를 보여주기 위해, 아마도 열 개의 새 파일을 생성하고 싶을 것입니다. 이 작업에 단일 runspace를 사용하면 첫 번째 파일을 생성한 다음 두 번째 파일로 이동하고, 그 다음은 세 번째 파일로 이동하는 식으로 모든 열 개의 파일을 생성할 것입니다. 이 예시의 스크립트블록은 아래와 같을 것입니다. 이 스크립트블록에 열 번의 파일 이름을 루프로 전달하면 모든 파일이 생성될 것입니다.
아래 예시에서는 이름을 받아 해당 이름으로 파일을 생성하는 짧은 스크립트가 포함된 스크립트 블록이 정의됩니다. 최대 5개의 스레드를 사용하여 runspace 풀이 생성됩니다.
다음으로, 루프가 열 번 반복되며 각 반복에서 순서 번호를 $_
에 할당합니다. 따라서 첫 번째 반복에서는 1, 두 번째에서는 2 등으로 할당됩니다.
루프는 PowerShell 개체를 생성하고, 스크립트 블록과 스크립트에 대한 인수를 할당하고 프로세스를 시작합니다.
마지막으로 루프의 끝에서는 대기하고 있는 모든 대기열 작업이 완료될 때까지 기다립니다.
이제 한 번에 하나씩 스레드를 생성하는 대신, 한 번에 다섯 개를 생성할 것입니다. runspace 풀을 사용하지 않으면 다섯 개의 별도의 runspace와 다섯 개의 별도의 Powershell
인스턴스를 생성하고 관리해야 합니다. 이러한 관리는 빠르게 난잡해집니다.
대신에 runspace 풀을 생성하고, PowerShell 인스턴스를 사용하며, 동일한 코드 블록과 루프를 사용할 수 있습니다. 다른 점은 runspace가 자체적으로 이 다섯 개의 스레드를 사용하도록 확장된다는 것입니다.
Runspace 풀 생성하기
런스페이스 풀을 생성하는 방법은 이전 섹션에서 생성한 런스페이스와 매우 유사합니다. 아래는 그 예입니다. 스크립트 블록의 추가와 invoke 프로세스는 런스페이스와 동일합니다. 아래에서 볼 수 있듯이, 런스페이스 풀은 최대 다섯 개의 스레드로 생성됩니다.
속도를 비교하기 위해 런스페이스와 런스페이스 풀을 비교해 보겠습니다.
이전에 사용한 Start-Sleep
명령을 실행하는 런스페이스를 생성합니다. 이번에는 10번 실행해야 합니다. 아래 코드에서 볼 수 있듯이, 5초 동안 대기하는 런스페이스가 생성됩니다.
단일 런스페이스를 사용하므로 작업이 완료될 때까지 대기해야 합니다. 이것이 작업이 완료될 때까지 100ms 대기가 추가된 이유입니다. 이 대기 시간을 줄일 수 있지만, 작업이 완료되었는지 확인하는 데 더 많은 시간을 소비하게 되어 성능 향상이 감소할 것입니다.
아래 예제에서는 5초 동안 10세트를 완료하는 데 약 51초가 걸렸습니다.

이제 단일 런스페이스 대신 런스페이스 풀을 사용하도록 변경해 보겠습니다. 아래에 실행될 코드가 있습니다. 런스페이스 풀을 사용할 때 아래 코드에서 두 가지 차이점을 볼 수 있습니다.
아래에서 볼 수 있듯이, 이 작업은 단일 런스페이스의 51초에 비해 약 10초가 걸립니다.

아래는 이 예제에서 런스페이스와 런스페이스 풀의 차이를 요약한 것입니다.
Property | Runspace | Runspace Pool |
---|---|---|
Wait Delay | Waiting for each job to finish before continuing to the next. | Starting all of the jobs and then waiting until they have all finished. |
Amount of Threads | One | Five |
Runtime | 50.8 Seconds | 10.1 Seconds |
PoshRSJob를 사용하여 Runspaces로 부드럽게 전환하기
A frequent occurrence when programming is that you will do what is more comfortable and accept the small loss in performance. This could be because it makes the code easier to write or easier to read, or it could just be your preference.
PowerShell에서도 일부 사람들은 사용의 편리성 때문에 PSJobs 대신 runspaces를 사용할 것입니다. 성능을 향상시키면서 사용하기가 어려워지지 않도록 차이를 나누는 몇 가지 방법이 있습니다.
PoshRSJob라는 널리 사용되는 모듈이 있습니다. 이 모듈에는 일반적인 PSJobs 스타일과 일치하는 모듈이 있으며 runspaces를 사용하는 추가 이점이 있습니다. runspaces와 powershell 개체를 생성하기 위한 모든 코드를 지정해야 하는 대신, PoshRSJob 모듈은 명령을 실행할 때 이를 처리하는 모든 작업을 처리합니다.
모듈을 설치하려면 관리자 권한으로 PowerShell 세션에서 아래 명령을 실행하십시오.
모듈이 설치되면 PSJob 명령과 동일한 명령이 있음을 알 수 있습니다. 대신 Start-Job
은 Start-RSJob
입니다. Get-Job
대신 Get-RSJob
입니다.
아래는 PSJob과 RSJob에서 동일한 명령을 실행하는 예입니다. 보시다시피 구문과 출력은 매우 유사하지만 완전히 동일하지는 않습니다.

아래는 PSJob과 RSJob의 속도 차이를 비교할 수 있는 코드입니다.
아래에서 보시다시피 RSJobs는 여전히 runspaces를 사용하고 있기 때문에 큰 속도 차이가 있습니다.

Foreach-Object -Parallel
파워쉘 커뮤니티는 프로세스를 빠르게 멀티스레드로 실행할 수 있는 간편하고 내장된 방법을 원해왔습니다. 이에 대한 결과로 parallel 스위치가 등장했습니다.
글을 쓰는 시점에서는 파워쉘 7은 아직 미리보기 상태입니다. 그러나 ForEach-Object 명령에 Parallel 매개변수가 추가되었습니다. 이 프로세스는 코드를 병렬화하기 위해 runspace를 사용하며, ForEach-Object에 사용된 스크립트 블록을 runspace의 스크립트 블록으로 사용합니다.
세부사항은 아직 작업 중이지만, 앞으로 runspace를 사용하는 더 쉬운 방법이 될 수 있습니다. 아래에서 여러 개의 sleep 세트를 빠르게 반복할 수 있는 것을 볼 수 있습니다.

멀티스레딩과의 도전
멀티스레딩은 지금까지 놀라운 것처럼 들려왔지만, 그렇지 않습니다. 멀티스레딩을 사용하는 모든 코드에는 많은 도전 과제가 있습니다.
변수 사용
멀티스레딩의 가장 크고 가장 명백한 도전 과제 중 하나는 변수를 전달하지 않고는 변수를 공유할 수 없다는 것입니다. 하지만 여기에는 동기화된 해시 테이블이라는 예외가 있으나, 이는 다른 시간에 이야기할 주제입니다.
PSJobs와 runspaces는 기존 변수에 대한 액세스 권한이 없으며, 콘솔에서 다른 runspaces에서 사용되는 변수와 상호작용할 수 있는 방법이 없습니다.
이는 이러한 작업에 동적으로 정보를 전달하는 데 큰 도전 과제가 됩니다. 멀티스레딩의 유형에 따라 답이 다릅니다.
PoshRSJob 모듈의 Start-Job
와 Start-RSJob
을 위해 ArgumentList
매개변수를 사용하여 스크립트 블록에 전달될 객체 목록을 지정할 수 있습니다. 아래는 PSJobs와 RSJobs에 사용되는 명령어 예시입니다.
PSJob:
RSJob:
네이티브 runspace는 동일한 편의성을 제공하지 않습니다. 대신 PowerShell 개체에 AddArgument()
메서드를 사용해야 합니다. 아래는 각각에 대한 예시입니다.
Runspace:
Runspace 풀은 동일한 방식으로 작동하지만, 아래는 runspace 풀에 인수를 추가하는 예시입니다.
로깅
멀티스레딩은 로깅에 대한 도전 과제를 동반합니다. 각 스레드가 서로 독립적으로 작동하기 때문에 모두 같은 위치에 로그를 기록할 수는 없습니다. 여러 스레드가 파일에 로그를 기록하려고 할 때 한 스레드가 파일에 기록하는 동안 다른 스레드는 기록할 수 없게 됩니다. 이로 인해 코드가 느려지거나 완전히 실패할 수 있습니다.
예를 들어, 아래 코드는 5개의 스레드를 사용하여 100번 로그를 단일 파일에 기록하려고 시도합니다.
출력에서는 오류를 볼 수 없지만, 텍스트 파일의 크기를 확인하면 아래와 같이 100개의 작업이 모두 올바르게 완료되지 않은 것을 알 수 있습니다.

이를 해결하는 방법 중 하나는 별도의 파일에 로그를 저장하는 것입니다. 이렇게 하면 파일 잠금 문제가 해결되지만, 모든 발생 내용을 파악하기 위해 정리해야 할 많은 로그 파일이 생깁니다.
다른 대안은 일부 출력의 타이밍을 늦출 수 있게하고 작업이 완료된 후에만 작업 내용을 기록하는 것입니다. 이렇게하면 원래 세션을 통해 모든 것을 직렬화 할 수 있지만, 모든 것이 어떤 순서로 발생했는지 정확히 알지 못하기 때문에 일부 세부 정보를 잃게됩니다.
요약
멀티 스레딩은 큰 성능 향상을 제공할 수 있지만, 때로는 머리아플 수도 있습니다. 일부 작업 부하는 크게 이익을 얻을 수 있지만, 다른 작업 부하는 전혀 이익을 얻지 못할 수도 있습니다. 멀티 스레딩을 사용하는 것에는 많은 장단점이 있지만, 올바르게 사용하면 코드의 실행 시간을 크게 줄일 수 있습니다.
더 읽기
Source:
https://adamtheautomator.com/powershell-multithreading/