[C#] 병렬 프로그래밍 Parallel Programming | 김예건님

김예건님의 병렬 프로그래밍 Parallel Programming 시리즈 글을 소개합니다.

병렬 프로그래밍과 비동기 프로그래밍의 차이점과
데이터 중심과 작업 중심의 병렬 처리 개념을 잘 설명해 주고 있는 귀한 자료입니다.

(1) 데이터 병렬화

(2) 작업 병렬화

(3) 데이터 흐름

(4) 데이터 흐름 예제

위의 글 말고도 김예건님의 보물 글이 많이 있으니까 둘러 보시길 추천 드립니다.

7 Likes

요즘 새로운 프로젝트하려고 연구하고 개발하느라, 보는 사람도 적을거라 생각하고 블로그 글 쓰는걸 게을리 하고 있었는데 열심히 써야겠네요. :grinning_face_with_smiling_eyes:

이전에 연구했던 내용들을 차근차근 작성해봐야 겠네요.

image

제 블로그 글에 관심가져 주셔서 감사합니다. 앞으로 더 열심히 쓸 동기부여가 되네요! :wink:

5 Likes

사실 아직 저는 병렬 프로그래밍과 비동기 프로그래밍의 차이를 잘 모르기 때문에 Task만 사용하여 병렬처리를 하고 있습니다. 어차피 멀티스레드, 멀티코어를 사용한다는 맥락에서 같아보이기 때문입니다.

아시겠지만 Parallel로 파티셔닝 하여 작업 처리를 하는 부분도 Skip,Take와 Task로 동일하게 구현가능합니다. 코드 가독성이 더 좋아서 저는 그냥 Task로 작업하는 편인데, 저희 회사에서 주로 병렬처리가 필요한 부분을 예로 들면,

전자상거래 플랫폼의 OpenAPI의 설계가,

  1. 특정 기간을 지정하여 그 기간 안에 등록된 상품의 ID만 전부 수신
  2. 수신된 상품ID의 상세 정보를 가져오는 API 호출 시 1회 호출당 최대 50개까지만 사용가능 할 때, 상품이 10000개가 등록되어있다면 API는 200번~10000번을 호출할 수 있게됩니다. (API 1회 호출당 상품ID를 1개씩만 사용한다면 10000번 호출해야하고 50개씩 사용한다면 200번 호출해야합니다.)
  3. 이 때 10000개에 대해 Linq로 Skip, Take를 이용해 50개씩 반복해주고, Task.Run으로 API를 호출하며, 이 Task를 List로 등록하여 Task.WhenAll 처리합니다.

글로 설명드린 코드 예제는 아래와 같은 방식입니다.

        static async Task Main(string[] args)
        {
            List<int> ids = new(10000);

            for (int i = 0; i < 10000; i++)
            {
                ids.Add(i);
            }

            int idx = 0;
            List<Task> tasks = new();

            while (idx < 100)
            {
                IEnumerable<int> partition = ids.Skip(idx).Take(50);
                tasks.Add(Task.Run(() =>
                {
                    foreach (var item in partition)
                    {
                        Console.WriteLine(item);
                    }
                }));
                idx += 50;
            }

            await Task.WhenAll(tasks);
        }

병렬처리와 비동기의 차이점에 대해서 갖고 계신 지식을 나눠주시면 감사드리겠습니다…

3 Likes

병렬처리를 저도 잘 안쓰긴 합니다만.
보통 병렬처리라고 나오는것들의 API 구조를 보면 공통점이
배열에 들어있는 데이터에 동일한 연산처리를 하거나 다수의 API를 동시에 실행 한다입니다.
대부분의 개발언어에서 parallel_for 형태를 보이고요.
가장 원초적인 병렬처리는 SIMD명령을 생각하시면 될것 같네요.
어쨋든 병렬처리는 할 수 있으면 상당히 효율적인데.
이게 데이터를 미리 일정한 형태로 만들어야 하는 등 쓰기가 진짜 드럽습니다.

비동기 처리는 이미 알고 계실거라고 보고요.

3 Likes

말씀하신 내용을 Task.WhenAll을 사용한 형태로 병렬처리가 가능한데 그럼 이것은 비동기 인가요 병렬처리인가요?

1 Like

제 의견은 TPL을 사용하지 않은 병렬 처리로 생각합니다.

2 Likes

제 생각은 아니라고 봅니다.
보통 병렬 프로그래밍으로 나뉘는 것들은 명확하게 사용하는 API들이 있습니다.
그런 API들 상당수가 하드웨어 가속이 이뤄집니다.
대표적인것이 PPL, SIMD, CUDA, OpenCL등이 있습니다. (PPL은 하드웨어 가속이 있는지 확실하지 않네요)
병렬 프로그래밍이라는 용어를 쓸 경우에는 저런 전용 API를 사용하는 경우로 한정짓지
않으면 사실 비동기 프로그래밍하고 차이점을 찾기가 엄청 애매해지지 않을까 싶네요.
그리고 API 이름 자체에서도 차이가 확실하게 있기도 합니다.
병렬 API들은 죄다 Parallel이 붙거나 P라도 붙습니다. 비동기 계열은 ASync가 붙죠.

제가 고집스런것이 SIMD, CUDA,… 등의 병렬 프로그래밍을 몇년간 다루다 보니 그렇습니다.

5 Likes

(저 또한 틀리게 이해한 부분이 있을지 모르겠습니다. 정중하게 피드백 주시면 감사하겠습니다.)

병렬과 비동기가 굉장히 비슷하게 들리는 부분이 있긴 한데, 제가 이해한 병렬 프로그래밍은 "동일 시점"에서 한 번에 여러 작업을 처리할 수 있도록 해주는 것이고, 비동기 프로그래밍은 "작업이 언제 처리될 지 한정할 수는 없지만 어쨌든 처리된다"로 이해하고 있습니다.

둘 사이의 컨셉 차이를 극명하게 드러내는 부분이 Azure Function의 사용자 개입 시나리오 같은 부분인데, 이 문서를 보면 Task TPL을 사용하지만 실제로 일어나는 일은 단순히 어떤 작업을 기다린다기보다, 이벤트가 발생하는 상황을 TPL로 우아하게 랩핑한 컨셉에 가깝거든요.

처리하기 나름이겠으나, 그래서 비동기 프로그래밍에서는 "컨텍스트 (맥락)"라는 개념이 중요한 것 같고요, 병렬 프로그래밍은 맥락에 해당하는 제어 지점이 처음부터 고정되어있다는 것이 다른 점인 것 같습니다.

6 Likes

다른 커뮤니티의 분들과도 의견을 나눠서 스스로 정리한 결과…

Task를 사용하는 자체는 비동기 프로그래밍으로 병렬처리라 보기엔 어렵지만, 제가 코드로 예제를 보인대로 Parallel API를 사용하지 않아도 특정시점에 파티셔닝을 할 수 있고, 작업의 완료시점을 await Task.WhenAll로 대기가 가능하다는 점에서,

Task.WhenAll 을 사용하면 비동기 방식으로도 병렬프로그래밍이 가능하다.

로 정리했습니다…

사실 병럴처리를 정말 닷넷에서 Parallel을 통해 하게했으면 이런 궁금증이 안 생겼겠지만, Task로도 멀티스레딩이 가능하게 해놨다보니 헷갈렸습니다 ㅠㅠ…

그리고 아래 글 때문에 Parallel과 Task의 차이가 더욱 모호해진 것도 있는 것 같습니다.

그래도 이 기회에 다시 한 번 개념을 정리하는 계기가 되었습니다.

5 Likes