[C#] 39. Linq를 이용한 병렬 처리(Parallel)를 사용하는 방법


Study/C#  2021. 9. 29. 19:38

안녕하세요. 명월입니다.


이 글은 C#에서 Linq를 이용한 병렬 처리(Parallel)를 사용하는 방법에 대한 글입니다.


이전 글에서 제가 Thread에 대해서 설명한 적이 있습니다.

link - [C#] 36. 스레드(Thread)를 사용하는 방법, Thread.Sleep 함수 사용법


쓰레드라는 것은 프로그램 내에서 여러가지 처리를 동시에 처리, 즉 동시 처리를 해서 시스템의 성능을 올리는 기능입니다.

그런데 이 쓰레드라는 건 개수가 무한정 늘어가는 것이 아니고 어느 정도 늘어나면 반대로 성능이 저하가 되어 개수 관리를 하는 ThreadPool이 존재합니다. 그런데 이 ThreadPool은 Join 기능이 없어 EventWaitHandle 클래스로 동기화 시켜주는 기능을 따로 작성해야 하는 불편함이 있습니다.


Linq 식에서는 이러한 것을 이 두가지를 보완하는 병렬 처리를 만들 수 있습니다.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력시 사용되는 텍스트
    public string Text { get; set; }
    // 반복문 횟수
    public int Count { get; set; }
    // Sleep의 시간틱
    public int Tick { get; set; }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 클래스의 리스트
      var list = new List<Node>();
      // 리스트에 인스턴스 추가
      list.Add(new Node { Text = "A", Count = 3, Tick = 1000 });
      list.Add(new Node { Text = "B", Count = 5, Tick = 10 });
      list.Add(new Node { Text = "C", Count = 2, Tick = 500 });
      list.Add(new Node { Text = "D", Count = 7, Tick = 300 });
      list.Add(new Node { Text = "E", Count = 4, Tick = 200 });
      // 리스트에 Parallel 설정
      list.AsParallel()
         // 병렬 처리 개수 설정 (최대 2개)
        .WithDegreeOfParallelism(2)
        // 병렬 처리 실행
        .ForAll(x =>
        {
          // 설정된 반복문의 횟수만큼
          for (int i = 0; i < x.Count; i++)
          {
            // 콘솔 출력
            Console.WriteLine(x.Text + " = " + i);
            // 설정된 Sleep 시간틱
            Thread.Sleep(x.Tick);
          }
          // 완료 콘솔 출력
          Console.WriteLine("Complete " + x.Text);
        });
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보시면 Node 클래스의 인스턴스를 List에 넣은 다음, 그냥 Linq 식의 AsParallel 함수를 호출하는 것으로 병렬 처리가 설정이 됩니다.

WithDegreeOfParallelism의 함수를 통해서 Parallel의 쓰레드 최대 개수를 설정하고 ForAll를 통해 실행합니다.


ForAll은 람다식으로 설정하여 파라미터는 List에 설정된 Node 인스턴스를 취득하여 실행됩니다.

여기서 보면 Thread와 ThreadPool의 단점이 보완이 되는 기능입니다.


Parallel 기능을 List에만 붙여서 사용하는 것이 아니고 static 클래스로 독립적으로도 사용 가능합니다.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Parallel static 클래스
      // 0부터 4까지
      Parallel.For(0, 5, x =>
      {
        // 0부터 2까지 반복
        for (int i = 0; i < 3; i++)
        {
          // 콘솔 출력
          Console.WriteLine(x + " = " + i);
          // 쓰레드 대기
          Thread.Sleep(1);
        }
      });

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

Parallel 클래스의 병렬 처리는 기본 형태는 For 함수입니다.

의미는 for의 초기식, 조건식, 증감식을 Parallel로 설정하는 것입니다.즉, for문을 병렬 처리하는 것입니다.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력시 사용되는 텍스트
    public string Text { get; set; }
    // 반복문 횟수
    public int Count { get; set; }
    // Sleep의 시간틱
    public int Tick { get; set; }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 클래스의 리스트
      var list = new List<Node>();
      // 리스트에 인스턴스 추가
      list.Add(new Node { Text = "A", Count = 3, Tick = 1000 });
      list.Add(new Node { Text = "B", Count = 5, Tick = 10 });
      list.Add(new Node { Text = "C", Count = 2, Tick = 500 });
      list.Add(new Node { Text = "D", Count = 7, Tick = 300 });
      list.Add(new Node { Text = "E", Count = 4, Tick = 200 });
      // Parallel static 클래스
      // list의 개수만큼
      Parallel.ForEach(list, x =>
      {
        // 설정된 반복문의 횟수만큼
        for (int i = 0; i < x.Count; i++)
        {
          // 콘솔 출력
          Console.WriteLine(x.Text + " = " + i);
          // 설정된 Sleep 시간틱
          Thread.Sleep(x.Tick);
        }
        // 완료 콘솔 출력
        Console.WriteLine("Complete " + x.Text);
      });
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

Parallel의 ForEach는 foreach문을 병렬로 처리하는 형식입니다.

형식은 첫번째 파라미터의 List 형식의 데이터를 넣으면 두번째 파라미터로 람다식으로 데이터를 취득하는 형태로 병렬 처리를 하는 것입니다.

using System;
using System.Threading.Tasks;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Parallel static 클래스
      // 4개의 람다식을 병렬 처리
      Parallel.Invoke(() =>
      {
        Console.WriteLine("A");
      }, () =>
      {
        Console.WriteLine("B");
      }, () =>
      {
        Console.WriteLine("C");
      }, () =>
      {
        Console.WriteLine("D");
      });
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위는 각기의 4개 람다식을 병렬 처리했습니다. Invoke함수의 파라미터와 params 형식이므로 가변적으로 람다식을 넣을 수 있습니다.


이 글을 작성하면서 느낀 부분인데 Linq 식의 AsParallel()은 저도 자주 사용하는 함수입니다만, Parallel 클래스는 별로 사용할 메리트가 전혀 없네요.

사실 저도 글을 쓰기 전에는 단순히 AsParallel를 static 스타일로 표현한 방법일 줄 알았는데.. 그것이 아니네요.

성능 상의 이점도 없고, 쓰레드를 관리할 수 있는 함수나 프로퍼티도 없고, 단순히 for문을 병렬로 처리하는 것인데.. 이렇게 사용할 바에 ThreadPool이 훨씬 낫지 않을까 싶네요.


여기까지 C#에서 Linq를 이용한 병렬 처리(Parallel)를 사용하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.