-
C# 병렬 실행 탐구프로그래밍 2023. 11. 2. 23:20
인간의 두뇌는 하나이지만, 컴퓨터의 두뇌는 이제 16개가 기본이다.
종종 우리는 특정한 시나리오에 대한 병렬 실행에 대한 요구를 구현해야 한다.
C#에서는 유용한 병렬 실행 기능을 제공하고 있으므로 해당 기능에 대해 알아보자.
- 요구사항
매일 약 100여개의 API를 호출하여 특정 Db에 적재하거나 요청 마다 조회 결과를 제공해야 한다. - 제한사항
모든 API는 최대한 빠르게 실행되어야 하고, 조회 결과를 리턴해야 할 경우 모든 Request는 약 5초 이내에 실행되어야 한다.
위와 같은 사항을 구현하려면 어떻게 해야 할까?
일반적으로 각각의 API 연동을 구현한 후 foreach로 실행할 것이다.
위 경우에는 제한 시간내에 처리하지 못 할 수 있고 모든 CPU를 사용하지 못 할 수 있다.따라서, C#에서는 Parallel.ForEach를 제공하고 있다.
Parallel의 구성 요소에 대해 알아보자.
- ForEach 파라메터는 아래와 같다.
1) Parallel.ForEach([IEnuerable<TSource>], [Action<TSource>])
2) IEnumerable<TSource>는 Loop 대상이 될 컬렉션 객체를 의미한다.
3) Action<TSource>는 Parallel이 Loop 실행 대상을 Callback형식으로 값을 넘겨주는 것을 의미한다.
구현해 보자.
public class Sample { public void ParallelRun() { var items = Enumerable.Range(1, 100).ToList(); Parallel.ForEach(items, item => { Console.WriteLine(item); }); } }
위 코드 샘플은 List<int>을 대상으로 병렬처리를 하고 있는 예제이다.
만약 위 코드가 foreach로 구현되었다면 List<int>의 Root부터 실행하는 것을 보장할 것이다.
하지만, 위 코드의 실제 결과는 1부터 100까지 무작위로 출력되는 것을 확인 할 수 있다.실제로 무작위라는 표현은 틀린 것이고 할당된 연산 순서만큼 동시 실행하고 있으므로, 만약 자신의 PC가 16Thread를 지원한다면 이론상 1부터 16까지 동시 출력된다고 이해하면 된다. (단, 순서가 보장되지는 않는다.)
자, 이제 처음 언급된 요구사항으로 돌아가 보자.
이제 우리는 100여개의 API를 동시 호출해야 한다.
예상되는 API 시나리오가 각 API당 1초 이내에 종료되고, 100개의 호출을 5초 이내에 처리해야 한다면, 32Thread가 필요하다고 예상되고, 이에따라 계산상 4초가 소요된다.
따라서 위 요구 조건을 충족하기 위한 최소 CPU 사양은 32 Thread를 지원해야 한다.
실제 구현 코드는 아래와 같다.
public class Sample { public void ParallelRun() { var items = new[] { "https://google.com", "https://naver.com", "https://medium.com/" }; CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var options = new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = cts.Token }; Parallel.ForEachAsync(items, options, async (item, token) => { await GetPageCall(item, token); }); Console.WriteLine(list.xJoin()); } private async Task<string> GetPageCall(string url, CancellationToken cancellationToken) { using var client = new HttpClient(); var res = await client.GetAsync(url, cancellationToken); return await res.Content.ReadAsStringAsync(cancellationToken); } }
위 예제는 도메인별 Get 리퀘스트를 전송하는 예제이다. (절대! 그대로 따라하지 말자.)
HttpRequest는 비동기가 기본이므로 Parallel.ForEachAsync로 구현한다.
Parallel 구현시 제한 사항이 있다면 반드시 CancellationTokenSource를 선언해야 한다.
또한 ParalleOptions에서 MaxDegreeOfParallelism을 선언할 수 있다.
이는 사용할 최대 CPU Thread를 의미한다. 기본값은 Max Process Count이다. 다만, Max로 선언한다고 해서매번 Max Thread를 사용하는 것이 아니라는 것을 기억하자.
Parallel은 내부적으로 ThreadPool에 실행할 목록을 적재하고 .net 기본 실행 스케줄에 따라 동작한다. 참고로 실행 스케줄도 선언할 수 있다.
위와 같이 Parallel에 대해 알아 보았다.
동시 실행에 대한 제한적 요구 사항이 있을 경우 우리는 아래와 같은 것을 떠올리자.
- 서버 또는 PC의 CPU 사양이 제한 사항의 최소 사양을 지원하는가?
- Parallel을 사용하여 해결할 경우 실행 우선 순위가 필요한가? (실행 순서)
- Parallel에서 Db 연결이 필요할 경우 Db가 충분한 Connection을 지원할 수 있는가?
- Socket 문제는?
다음 기회에 좀 더 깊은 내용을 알아보자.
'프로그래밍' 카테고리의 다른 글
Movin'In 모바일이 포함된 임대 부동산 관리 플랫폼 (0) 2023.11.06 .Net HTTP 라이브러리 비교 (0) 2023.11.06 static method? static class? (0) 2023.11.02 두번 개발하지 않는 방법 (0) 2023.11.02 if else if else if else, no more (0) 2023.11.02 - 요구사항