System.Threading.Tasks.Parallel 클래스
System.Threading.Tasks.Parallel 클래스는 C#에서 병렬 프로그래밍을 쉽게 구현할 수 있도록 도와주는 클래스이다.
마치 여러 명의 일꾼이 함께 일하는 것처럼, Parallel 클래스는 작업을 여러 개의 작은 작업으로 나누어 여러 스레드에서 동시에 실행할 수 있도록 한다. Parallel 클래스를 이용해서 병렬로 어떤 메서드를 호출하는 것이다. 이를 통해 작업 속도를 높이고 프로그램 성능을 향상시킬 수 있다.
Parallel 클래스는 System.Threading.Tasks 네임스페이스에 포함되어 있으며, 다음과 같은 주요 메서드를 제공한다.
Parallel.For(): for 루프를 병렬로 실행한다.Parallel.ForEach(): foreach 루프를 병렬로 실행한다.Parallel.Invoke(): 여러 개의 메서드를 병렬로 실행한다.Parallel.For()
Parallel.For() 메서드는 for 루프를 병렬로 실행한다. 즉, 반복 횟수만큼 작업을 여러 스레드에서 동시에 실행할 수 있다.
using System;
using System.Threading.Tasks;
Parallel.For(0, 10, i =>
{
Console.WriteLine($"현재 스레드 ID: {Thread.CurrentThread.ManagedThreadId}, i: {i}");
// i 값을 사용하여 작업 수행
});
이 코드는 0부터 9까지의 숫자를 사용하여 작업을 수행하는데, Parallel.For() 메서드를 사용하여 각 숫자에 대한 작업을 병렬로 실행한다. i는 반복 변수이고, 람다 식 => 이후의 코드 블록은 각 반복에서 실행될 작업을 정의한다.
void SomeMethod(int i)
{
console.WriteLine(i);
}
// ...
Parallel.For(0, 100, SomeMethod);
이 코드에서 Parallel.For() 메서드는 SomeMethod() 메서드를 병렬로 호출하면서 0부터 100 사이의 정수를 메서드의 인수로 넘긴다.
SomeMethod() 메서드를 병렬로 호출할 때 몇 개의 스레드를 사용할 지는 Parallel 클래스가 내부적으로 판단하여 최적화하므로, Parallel 클래스를 편하게 사용할 수 있는 것이다.
Parallel.ForEach()
Parallel.ForEach() 메서드는 foreach 루프를 병렬로 실행한다. 즉, 컬렉션의 각 요소에 대한 작업을 여러 스레드에서 동시에 실행할 수 있다.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5 };
Parallel.ForEach(numbers, number =>
{
Console.WriteLine($"현재 스레드 ID: {Thread.CurrentThread.ManagedThreadId}, number: {number}");
// number 값을 사용하여 작업 수행
});
이 코드는 numbers 리스트의 각 요소를 사용하여 작업을 수행하는데, Parallel.ForEach() 메서드를 사용하여 각 요소에 대한 작업을 병렬로 실행한다. number는 현재 요소를 나타내고, 람다 식 => 이후의 코드 블록은 각 요소에 대해 실행될 작업을 정의한다.
Parallel.Invoke()
Parallel.Invoke() 메서드는 여러 개의 메서드를 병렬로 실행한다. 즉, 여러 개의 작업을 동시에 실행할 수 있다.
using System;
using System.Threading.Tasks;
Parallel.Invoke(
() => { Console.WriteLine($"첫 번째 작업, 스레드 ID: {Thread.CurrentThread.ManagedThreadId}"); },
() => { Console.WriteLine($"두 번째 작업, 스레드 ID: {Thread.CurrentThread.ManagedThreadId}"); },
() => { Console.WriteLine($"세 번째 작업, 스레드 ID: {Thread.CurrentThread.ManagedThreadId}"); }
);
이 코드는 세 개의 람다 식을 Parallel.Invoke() 메서드에 전달하여 세 개의 작업을 병렬로 실행한다. 각 람다 식은 하나의 작업을 정의한다.
Parallel 클래스를 사용할 때 주의 사항
lock 키워드 등을 사용하여 동기화해야 한다.Parallel 클래스는 작업의 실행 순서를 보장하지 않는다. 따라서 작업 순서가 중요한 경우에는 Parallel 클래스를 사용하지 않는 것이 좋다.Parallel 클래스를 사용하는 것이 오히려 성능을 저하시킬 수 있다.Parallel 클래스는 C#에서 병렬 프로그래밍을 구현하는 데 유용한 도구이지만, 위와 같은 주의 사항을 염두에 두고 사용해야 한다.
예제 코드
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ParallelLoop
{
class MainApp
{
static bool IsPrime(long number) // 주어진 수가 소수인지 판별하는 메서드이다.
{
if (number < 2) // 2보다 작으면 소수가 아니다.
return false;
if (number % 2 == 0 && number != 2) // 2를 제외한 짝수는 소수가 아니다.
return false;
for (long i = 2; i < number; i++) // 2부터 number - 1까지의 수로 나누어 떨어지는지 확인한다.
{
if (number % i == 0) // 나누어 떨어지면 소수가 아니다.
return false;
}
return true; // 위 조건을 모두 만족하면 소수이다.
}
static void Main(string[] args)
{
long from = Convert.ToInt64(args[0]); // 명령줄 인수의 첫 번째 값을 long 타입으로 변환하여 from에 저장한다.
long to = Convert.ToInt64(args[1]); // 명령줄 인수의 두 번째 값을 long 타입으로 변환하여 to에 저장한다.
Console.WriteLine("Please press enter to start..."); // "Please press enter to start..." 메시지를 출력한다.
Console.ReadLine(); // 사용자 입력을 기다린다.
Console.WriteLine("Started..."); // "Started..." 메시지를 출력한다.
DateTime startTime = DateTime.Now; // 현재 시간을 startTime에 저장한다.
List<long> total = new List<long>(); // 찾은 소수를 저장할 List<long> 객체를 생성한다.
Parallel.For(from, to, (long i) => // from부터 to - 1까지의 숫자에 대해 병렬로 반복 작업을 수행한다.
{
if (IsPrime(i)) // i가 소수이면
lock (total) // total 객체에 락을 걸어 동기화한다.
total.Add(i); // total 리스트에 i를 추가한다.
});
DateTime endTime = DateTime.Now; // 현재 시간을 endTime에 저장한다.
TimeSpan ellapsed = endTime - startTime; // startTime과 endTime의 차이를 계산하여 ellapsed에 저장한다.
// from부터 to까지의 소수 개수와 경과 시간을 출력한다.
Console.WriteLine("Prime number count between {0} and {1} : {2}", from, to, total.Count);
Console.WriteLine("Ellapsed time : {0}", ellapsed);
}
}
}
코드 설명
이 C# 코드는 지정된 범위 내에서 소수(prime number)를 찾는 프로그램이다. Parallel.For 메서드를 사용하여 작업을 병렬로 처리하고, 각 스레드에서 찾은 소수를 total 리스트에 저장한다.
IsPrime(long number) 메서드: 입력으로 받은 number가 소수인지 판별하여 bool 값을 반환한다.Main 메서드:from, to)를 가져온다.Parallel.For 메서드를 사용하여 from부터 to - 1까지의 숫자에 대해 병렬로 반복 작업을 수행한다.IsPrime() 메서드를 호출하여 현재 숫자가 소수인지 판별한다.lock (total) 문을 사용하여 total 리스트에 숫자를 추가한다. lock 문은 여러 스레드가 동시에 total 리스트에 접근하여 데이터 경쟁이 발생하는 것을 방지한다.from부터 to까지의 소수 개수와 경과 시간을 출력한다.Parallel.For 메서드
Parallel.For 메서드는 지정된 범위의 숫자에 대해 반복 작업을 병렬로 수행한다.Parallel.For 메서드는 System.Threading.Tasks 네임스페이스에 정의되어 있다.주의 사항
Parallel.For 메서드를 사용할 때는 데이터 경쟁이 발생하지 않도록 주의해야 한다.lock 문을 사용하여 공유 자원에 대한 접근을 동기화할 수 있다.예제 코드 실행 결과
오류 발생
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
원인 파악
System.IndexOutOfRangeException 오류는 배열의 범위를 벗어나는 인덱스에 접근하려고 할 때 발생하는 예외이다.
이 코드에서는 Main 메서드의 첫 부분에서 args[0]과 args[1]에 접근하여 명령줄 인수를 가져오려고 시도한다. 하지만 명령줄 인수를 입력하지 않고 프로그램을 실행하면 args 배열이 비어 있으므로 IndexOutOfRangeException 예외가 발생한 것이다.
이 문제를 해결하려면 프로그램을 실행할 때 명령줄 인수를 입력해야 한다.
Visual Studio에서 명령줄 인수를 입력하는 방법은 다음과 같다.

또는, 명령 프롬프트 또는 터미널에서 프로그램을 실행할 때 명령줄 인수를 입력할 수도 있다.
프로그램이름 인수1 인수2 ...
예를 들어, ParallelLoop.exe 프로그램을 실행하면서 1부터 100까지의 소수를 찾으려면 다음과 같이 입력한다.
ParallelLoop.exe 1 100
이렇게 하면 IndexOutOfRangeException 예외가 발생하지 않고 프로그램이 정상적으로 실행된다.
해결
1부터 100까지의 소수를 찾기 위해 명령줄 인수에 1 100을 입력하였고, 다음은 그 실행 결과이다.
