방프리

23.11.08 C# 동시성 프로그래밍 (병렬 처리) 본문

C#/동시성 처리

23.11.08 C# 동시성 프로그래밍 (병렬 처리)

방프리 2023. 11. 8. 00:45

1. 데이터의 병렬처리

 

- 병렬로 여러 데이터를 반복문을 통해 처리할 때

- 만약 공유하는 변수 혹은 리소스가 있다면 잠금(lock)을 사용해보는 것도 좋다.

- 책에서는 ForEach 위주로 설명하였지만 순차처리를 목적으로 한다면 ForEachAsync도 고려해볼만 하다.

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees,
    CancellationToken token)
{
	Parallel.ForEach(matrices,
    	new ParallelOptions { CancellationToken = token },
        matrix => matrix.Rotate(degress));
}

 

2. 병렬 집계

 

- 병렬 작업이 끝난 후 결과를 집계할 때

int ParallelSum(IEnumerable<int> values)
{
    object mutex = new object();
    int result = 0;
    Parallel.ForEach(source: values,
    	localInit: () => 0,
        body: (item, state, localValue) => localValue + item,
        localFinally: localValue =>
        {
        	lock (mutex)
            {
            	result += localValue;
            }
        });
    return result;
}

//위의 집계를 PLINQ를 통해 한 줄로 요약
 int ParallelSum(IEnumerable<int> values)
 {
 	return values.AsParallel().Sum();
 }
 
 //기본 지원 집계가 아닌 제네릭 형식의 집계를 원한다면
 int ParallelSUm(IEnumerable<int> values)
 {
 	return values.AsParallel().Aggregate(
    	seed: 0,
        func: (sum, item) => sum + item
 }

 

3. 병렬 호출

 

- 독립적인 여러 개의 메서드를 병렬로 호출할 때

- CancellationToken을 통해 중간 취소도 지원이 가능하다.

 

void DoAction20Times(Action action, CancellationToken token)
{
    Action[] actions = Enumerable.Repeat(action, 20).ToArray();
    Parallel.Invoke(new ParallelOptions { CancellationToken = token }, actions);
}

 

4. 동적 병렬 처리

 

- 작업의 구조와 개수에 따라 서로 다르게 적용

- 서로 연관이 있는 작업들 중 선, 후가 나뉘는 작업이라면 TaskCreationOptions.AttachedToParent

- 연속 작업을 사용해서 바로 다른 작업의 예약을 해야한다면 Task.COntinueWith

 

// 두 작업 A와 B는 부모,자식 관계를 가진다
void Traverse(Node current)
{
    DoExpensiveActionOnNode(current);
    if (current.Left != null)
    {
        Task.Factory.StartNew(
            () => Traverse(current.Left),
            CancellationTOken.None,
            TaskCreationOptions.AttachedToParent,
            TaskScheduler.Default);
    }
    if (current.Right != null)
    {
    	Task.Factory.StartNew(
            () => Traverse(current.Right),
            CancellationToken.None,
            TaskCreationOptions.AttachedToParent,
            TaskScheduler.Default);
    }
}

void ProcessTree(Node root)
{
    Task task = Task.Factory.StartNew(
    	() => Traverse(root),
        CancellationToken.None,
        TaskCreationOptions.None,
        TaskScheduler.Default);
    task.Wait();
}

// 두 작업 A와 B는 연속 작업 관계를 가진다
void ContinueTask()
{
    Task task = Task.Factory.StartNew(
    	() => Thread.Sleep(TimeSpan.FromSecond(2)),
        CancellationToken.None,
        TaskCreationOptions.None,
        TaskScheduler.Default);
    Task continuation = task.ContinueWith(
    	t => Trace.WriteLine("Task is done"),
        CancellationToken.None,
        TaskContinuationOptions.None,
        TaskScheduler.Default);
}

 

5. PLINQ

 

- 기본적으로 Parallel이 좀 더 프로세스에 친화적이지만 PLINQ의 표현의 간결함과 사용 편의성을 고려한다면 LINQ보다

성능이 더 뛰어남

 

IEnumerable<int> MultiplyBy2(IEnumerable<int> values)
{
    return values.AsParalle().Select(value => value * 2);
}

IEnumerable<int> MultiplyBy2(IEnumerable<int> values)
{
    return values.AsParallel().AsOrderd().Select(value => value * 2);
}

int ParallelSum(IEnumerable<int> values)
{
    return values.AsParallel().Sum();
}
Comments