지난 포스팅에서 Parallel을 사용하여서 병렬처리하는 방법에 대해서 이야기 했습니다. 아래 글을 참고하시면 볼 수 있습니다. 

https://math-development-geometry.tistory.com/37

불러오는 중입니다...

이번에는 병렬처리시 Thread에 안전한 ConcurrentBag을 사용하는 방법에 대해서 이야기해보겠습니다. 우선 그전에 ConcurrentBag이란 무엇인지 알아보겠습니다. ConcurrentBag은 평소 자주 사용하는 List을 병렬처리하여 추가하거나 삭제하는 경우에 데이터가 안전하게 보관되도록 만든 클래스입니다. 사용법은 아주 간단한데요. List와 똑같이 사용하면 됩니다. 

List와 같은 방법으로 선언하여 데이터를 추가하였습니다. 현재 각 공간에 1,2,3,4 가 들어있습니다. 여기서 삭제하는 것을 알아보면 List 에서는 Remove, RemoveAt, RemoveAll과 같은 함수가 있습니다. 이때 List에서는 데이터 삭제가 일어나면 각 item들의 인덱스와 링크 주소가 바뀌게 됩니다. 그렇기 때문에 한개의 item을 삭제하고 있는 중에 다른 item을 삭제하면 문제가 생기게 됩니다. 평소 병렬 프로그래밍을 하지 않았다면 전혀 문제가 되지 않았을 것입니다. 왜냐하면 한개를 삭제하고 삭제가 끝나면 다음 item을 삭제하기 때문이죠.

하지만 병렬처리를 하게 되면 한개의 item이 삭제되고 있는 중에 다른 item이 삭제될 수 있어요. 그렇기 때문에 에러가 생기게 됩니다. 이때 에러를 막기 위해서 사용하는 것이 ConcurrentBag인 것입니다. 여기에는 List에 없는 함수가 있는데요. TryPeek과 TryTake입니다. 함수명에 Try가 있기 때문에 반환값은 bool 값인데요. 해당 함수가 성공하면 true 실패하면 false를 반환하는 것을 알 수 있습니다. TryPeek 함수는 해당 ConcurrentBag에서 가장 마지막에 추가된 item이 무엇인지 out 키워드를 통해서 반환하는 함수입니다. 반환할때 해당 ConcurrentBag에서 item이 삭제되지 않습니다. 반대로 TryTake함수는 마지막에 추가된 item이 반환되면서 해당 ConcurrentBag에서 삭제가 됩니다.

그렇기 때문에 필요한 함수를 잘 선택하여 사용하는 것이 좋습니다. 

실제 List를 사용하여 Parallel를 돌리게 되면 다음과 같이 에러 메세지가 생길 수 있습니다. 하지만 ConcurrentBag을 이용하면 같은 코드지만 문제 없이 돌아갑니다.

사실 이 내부를 구현하기 위해서는 다양한 작업이 필요한데요. C# 같은 경우에 이런 번거로운 작업을 대신 해주고 있기 때문에 정말 편하게 사용할 수 있는 언어입니다. 단 내부가 어떻게 돌아가고 어떤 방식인지 알고 사용하는 것과 모르고 사용하는 것은 큰 차이가 있겠죠. 다음에 기회가 되면 내부가 어떤식으로 작동하는지에 대해서 이야기해보겠습니다.

컴퓨터가 발달되면서 다양한 기법들이 생기고 있습니다. 그 중에서 가장 중요하면서 당연한 기법이 병렬처리입니다. 대부분의 사람들은 한 순간에 한가지 일을 하는걸 힘들어 하지만, 컴퓨터는 한번에 여러가지일을 쉽게 처리할 수 있도록 설계되어 있는데요. 여러개의 cpu에 동시에 작업을 하도록 하여 처리 속도를 올리는 것입니다. 그렇다면 C#에서는 어떻게 병렬처리를 할 수 있는지 알아보겠습니다.

.Net 4.0에서 Parallel 이라는 것이 지원되었습니다. Parallel은 병렬 프로그래밍을 쉽게 할 수 있도록 만든 클래스입니다. Parallel은 System.Threading.Task에 가면 찾을 수 있습니다. Parallel에서 가장 많이 사용하는 것은 For와 ForEach입니다. 

우선 Parallel.For부터 살펴보면, 어떤 인덱스를 갖고 0번부터 100번까지 { } 안에 작업을 병렬로 처리하겠다는 뜻입니다. 기존의 for문은 index가 증가하거나 감소하면서 순서대로 하나씩 작업을 실행하지만, 위의 경우는 작업을 여러개로 나눠 동시에 실행하기 때문에 순서대로 실행되지 않습니다. 대신 더 빠른 속도로 작업을 끝낼 수 있습니다. 

ForEach를 사용하여서도 병렬처리를 할 수 있는데요. 이때는 List와 같이 여러개의 데이터가 들어가 있는 공간이 필요합니다. 예를 들어서 반 아이들의 키를 저장하고 있는 List가 있다고 할때, 이 키를 이용해서 바지 사이즈를 자동으로 측정하는 프로그램을 만든다고 가정해보죠. 이때 반 아이를 0번부터 100번까지 for문을 돌리면서 한명씩 측정할 수 있겠죠. 하지만 이보다 더 빠르게 하기 위해서는 ForEach를 이용해서 동시에 여러명을 측정하는 것이 가능해지는 것이죠. 이때 측정하는 함수는 각각 다른 메모리에서 서로의 데이터를 공유하면 안되는데요. 이 이야기는 뒤에 Invoke에 대한 설명을 보시면 이해가 될 것입니다. 

마지막으로 많이 사용하는 병렬처리 방법 중에 Invoke가 있습니다. Invoke는 특정 동작을 병렬로 실행할 수 있는데요. 

사용법은 이렇게 사용합니다. 동시에 함수 1~5를 실행한다는 함수입니다. 사용법은 정말 쉽습니다. 병렬처리를 하는데 있어서 가장 중요한 것은 메모리를 관리하는 것인데요. 만약에 각 함수가 실행되면서 같은 값에 접근하면 어떻게 될까요? 예를 들면 택시요금을 측정하는 기계를 만들었다고 할께요. 이 택시 요금 측정기는 인터넷에 연결되어 있어서 각 택시마다 요금이 어떻게 측정되는지 관리하고 기록할 수 있어요. 그런데 서버에 한 메모리에 가격이라는 변수가 있는데, 모든 택시에서 한 변수에만 접근해서 가격을 올리거나 내리거나 초기화한다면 어떻게 될까요? 아마 어떤 사람은 택시 요금이 10만원이 나오고 어떤 사람은 기본료만 내는 상황이 생길 수 있겠죠. 아마 자기 마음대로 요금이 바뀔 것 입니다. 각 택시들은 각자 독립적인 메모리를 갖고 있어야 안전하게 요금을 관리 할 수 있습니다.

이렇듯이 병렬처리를 할때는 입력과 출력을 정확하게 구분짓고 함수 내부에서 사용되는 메모리가 서로 공유되지 않도록 하는 것이 중요합니다. 물론 의도적으로 공유할 수 있지만 좋은 방법은 아니라고 생각됩니다. 

+ Recent posts