본문으로 바로가기
반응형

C# 언어에 대해 공부하다 보면, Process와 Thread에 대한 내용을 학습하게 되는데요. 시작부터 왠 C#이나 Process에 관한 내용을 이야기하는 것은 이번 글에서 작성할 주제와 연결되기 때문인데요. Process와 Thread에 대해 잘 모르는 분들을 위해서 잠깐 부연 설명을 드리고 넘어가겠습니다. (이미 Process와 Thread에 대해 알고 계신 분들은 본론으로 바로 넘어가셔도 됩니다.)

일반적으로 Process와 Thread를 오피스텔에 비유해서 많이들 설명하는데요. 오피스텔이 Process이고, Thread가 해당 오피스텔을 구성하는 상가나 사무실 같이 분리된 공간을 의미합니다. 일단 오피스텔은 외벽을 통해 다른 영역과 구분이 되어 있습니다. 그리고, 전기나 수도 같은 자원을 공급하지요. 같은 동네 안에 다른 오피스텔이 있을 수도 있지만, 서로 직접적인 왕래는 잘 일어나지 않습니다. 필요에 따라 택배라던지, 우편 등을 통해 다른 오피스텔과 소통은 가능할지 몰라도, 상대 오피스텔의 전기나 수도와 같은 자원을 직접적으로 공유하지는 않지요.

반면에 오피스텔에는 여러 입주자들이 있을 수 있습니다. 이들은 오피스텔에서 분리해 둔 작은 독립적인 공간을 가지지만, 어떤 입주자의 경우에는 사무실을 여러 개 붙여 놓은 정도의 큰 공간을 사용할 수도 있고, 어떤 입주자는 작은 공간을 사용할 수도 있습니다. 입주자들은 자신이 사용하는 공간에 책상이나 컴퓨터와 같은 사무기기를 둘 수 있고, 오피스텔에서 제공하는 전기나 수도와 같은 자원을 같이 사용하면서 자신만의 업무를 진행합니다. 그리고 오피스텔이라는 같은 공간 안에 있기 때문에 직접적인 왕래도 편하게 이루어질 수 있지요.

비유한 내용을 현실에 맞게 정리하자면, Process는 하나 이상의 Thread로 구성될 수 있지만, Thread는 하나 이상의 Process에 포함될 수는 없습니다. 그리고 Process는 운영체제로부터 자원을 할당받아서 사용하구요. Thread는 Process 내부에서 자원을 공유하면서 실행됩니다. 각 Process는 독립된 메모리 영역을 가지고 있기 때문에, 서로 다른 Process의 메모리 영역에 접근할 수는 없지만, 같은 Process 안에 있는 Thread들끼리는 메모리 영역을 공유할 수 있습니다.


Process와 Thread에 대해 요약된 내용을 읽어보셨으니, Process가 무엇이고, Thread는 어떤건지 완벽하게는 아니더라도 대략적인 개념은 이해하셨으리라 생각합니다. 그렇다고 해서, C#으로 Multi-thread 프로그램을 작성하라고 하면, 바로 작성할 수 있는 것은 아니지요. 하지만, 랩뷰에서는 아주 간단하게 Multi-thread 프로그램을 작성할 수 있습니다.

위 그림처럼 말이지요.

위 그림의 프로그램에서는 2개의 WHILE 구조가 블록 다이어그램에 작성되어 있습니다. 각 WHILE 구조에서는 난수 발생기를 웨이브폼 차트에 연결하였구요. 각기 다른 대기 시간을 적용하고 있습니다. 이렇게 작성된 프로그램을 실행시켜 보면, 그림에서 보시는 것과 같이 대기 시간의 차이만큼 실행 횟수가 차이나는 것을 볼 수 있는데요. 하나의 프로그램이 실행되는 동안 두 개의 WHILE 구조가 서로 다른 사이클로 동작했기 때문에 나타나는 현상입니다. 마치 Thread 처럼 말이지요.

이런 디자인 패턴을 병렬 루프 구조라고 부르는데요. 보시는 것처럼 복수의 WHILE 구조를 따로 구성함으로써 간단히 Multi-thread의 효과를 얻을 수 있습니다. 하지만, 이러한 구조는 Process의 메모리를 같이 공유할 수 있다는 점 때문에, 동일한 데이터 또는 메모리 접근 과정에서 문제가 발생할 수 있습니다. 다시 말해, 서로 다른 루프 사이에 데이터 공유가 없는 그런 독립적인 작업을 처리할 때 적합한 구조라고 말씀드릴 수 있구요. 서로 같의 데이터 공유가 필요한 경우에는 메모리 점유에 대한 우선 순위를 두는 등의 부수적인 작업이 필요합니다.

병렬 루프 구조는 프로그램을 작성하는 과정에서 생각보다 자주 만나게 되는데요. 처리해야 하는 작업의 특성이나 실행 주기에 따라 여러 개의 WHILE 구조로 나눠서 프로그램을 작성하게 됩니다. 그런데 같은 Process 안에서 실행되는 프로그램이 서로 데이터를 공유하지 않고 독립적으로 실행되는 경우가 그렇게 흔하지 않다는 점이 문제인데요. 세마포어 등을 이용해서 특정 자원에 대한 접근을 선점하는 방식으로도 물론 문제를 해결할 수도 있지만, 생산자/소비자라는 디자인 패턴을 통해 프로그램을 작성할 수도 있습니다.

생산자/소비자 디자인 패턴으로 작성된 프로그램의 예를 위 그림으로 보여드리고 있는데요. 보시는 것처럼 2개의 WHILE 구조로 프로그램이 작성되어 있어서 마치 병렬 루프 구조처럼 보이고 있지만, 생산자/소비자 디자인 패턴의 프로그램입니다. 그림에서 상단에 있는 WHILE 구조가 생산자 루프이고, 하단에 있는 WHILE 구조가 소비자 루프인데요. 일명 FIFO (First In First Out) 레지스터라고도 불리는 유명한 자료 구조 중 하나인 큐 (Queue)로 연결되어 있구요. 생산자 루프에서 생산되는 데이터를 소비자 루프가 받아서 처리하는 방식으로 이해하시면 됩니다. 다시 말해, 위의 프로그램에서는 생산과 소비에 대한 부분을 이해하기 위해 캡쳐라는 버튼을 이용해서 사용자가 직접 데이터를 생성하고 있으나, 실무에 적용할 때는 생산자 루프가 실시간으로 데이터를 계속해서 큐에 추가하고, 소비자 루프가 업데이트되는 데이터를 처리하는 방식으로 동작하게 됩니다.

생산자/소비자 패턴에서는 병렬 루프 구조처럼 2개의 WHILE 구조가 독립적인 루프를 구성하고 있으나, 실제로는 소비자 루프의 동작이 생산자 루프의 동작에 종속되는 형태를 보입니다. 그래서, 타이밍 함수를 생산자 루프에만 추가해 놓은 것을 보실 수 있구요. 실제로 이렇게 프로그램을 작성하고, 실행 하이라이트 기능을 켠 상태로 프로그램을 실행시켜 보면, 소비자 루프의 동작이 생산자 루프의 동작에 종속된다는 것을 확인하실 수 있습니다.

물론, 병렬 루프 구조에서 데이터를 공유하는 방식이 반드시 생산자/소비자 패턴만 있는 것은 아니지만, 개인적으로는 큐라는 버퍼에 전달할 데이터를 적층하는 방식으로 프로그램을 작성할 수 있으므로, 데이터를 확실하게 전달할 수 있다는 이점도 가지고 있고, 한 번에 여러 데이터를 순차적으로 전달할 수 있다는 이점도 가질 수 있어서 생산자/소비자 패턴에 대한 내용만 다루었다는 점 참고하시기 바랍니다.

 

반응형