Concurrency와 Parallelism 차이 (in Programming Language)

Language들을 접하다 보면 Concurrency와 Parallelism이란 단어를 접하게 되는데, 한국어로 변역하자면 동시성과 평행성 혹은 동시 실행과 평행 실행인데, 이 둘은 어떤 차이일까?

그 둘의 차이점을 살펴 보려면 먼저 Thread와 Process의 차이부터 논해야 한다. Process와 Thread의 차이는 신입이나 사회 초년생 개발자들이라면 면접 때 한두번은 들어본 질문일 것이다.
Thread는 Process와 거의 유사하다. 단지 Thread는 컴퓨터의 자원, 예를들면 메모리와 IO 장치들을 Thread끼리 공유하고, Process는 독립적으로 OS로부터 할당받아 사용한다. 일반적으로 Process는 여러 Thread를 Invoking 시키고 관리한다. 어플리케이션이 실행될 때 한개의 Process가 발생을 하고 그 Process가 어떠한 일을 할 때 컴퓨터의 자원을 최대로 활용하기 위해서는 병렬적으로 일을 하게 만들어야 한다. 그러므로 Thread를 이용해서 컴퓨터의 놀고 있는 자원들을 최대한으로 사용하게 만드는 것이 Multi-Thread Programming의 영역이다. I/O를 기다리게 하지 않고 다른 일을 하게 하거나, 놀고 있는 여분의 CPU 코어들을 최대한 사용하게 하는 것이 핵심이다.

C++ 11 버젼이 나오면서 기존 Boost 라이브러리의 편리했던 점들이 대거 채용이 되었고 Thread 부분도 pthread까지 내려가지 않고도 쉽게 thread를 사용할 수 있게 되었다. 하지만 그럼에도 불구하고 thread 를 사용하다 보면 join, detach등의 시점들을 설계하고,  mutex_lock, mutex_unlock으로 리소스들을 락킹하고 해제하고, 필요하면 세마포어도 구현해서 최적의 퍼포먼스를 낼 수 있도록 해주는 일이 어플리케이션이 무거워지고 커질 수록 그리 간단한 일이 아님을 알게된다. 자연히 이런 작업을 하다보면 한두번쯤은 데드락과 사랑에 빠져본 적이 있을 것이다.

Language에서 Parallelism이라 불리워지는 대표적인 예가 바로 Thread이다. Thread는 그야말로 동시에 평행적으로 실행이 된다. 그러므로 그만큼 효율적으로 일을 해낸다.

그렇다면 Concurrency 즉, 동시성이란 무엇일까? Concurrency는 Parallelism의 난해함을 풀어낸 방법이라고 말하고 싶다. 대표적인 것이 Coroutine이다. 유니티에서 코루틴은 서브루틴의 일종으로 진입 시점이 여러개인 서브루틴을 말한다. 참고 Link

이 코루틴은 thread와는 달리 평행하게 일을 수행하지 않는다. 한개의 Process가 Coroutine에 설정된 조건에 따라서 일반 서브루틴들과 왔다갔다 하면서 Task를 처리하는 방식이다. 그럼에도 불구하고 특정 라인이 수행된 다음 차근차근 수행되는 Sync 방식이 아니라 Async하게 코드를 실행시키고, 결과 값이 오거나 혹은 필요한 때에 다음 루틴을 실행 시키게 할 수 있어서 효율적으로 자원을 활용할 수 있다. 덤으로 코드가 Thread로 구현했을 때보다 훨씬 깔끔하게 나오고, Locking/UnLocking 메커니즘을 싹 걷어낼 수 있게 된다.

최신 언어라고 할 수 있는 Go에서도 이 Concurrency의 개념을 언어에 적극적으로 집어 넣었다. goroutine이 바로 그것인데 이걸 사용하면 multi-thread의 백단 dirty한 부분에 손 하나 대지 않고 쉽게 Async한 일들을 처리할 수 있게 해준다.

위의 Go언어의 goroutine을 사용한 샘플을 실행 시켜 보면 출력은 아래와 같다.

스크린샷 2015-05-11 오전 12.29.31

그런데 goroutine이 각각 독립적으로 움직이는 것을 확인하려면 for loop의 범위를 늘려 주면 되는데 10000개 정도 줘보면 going이라는 단어가 중간에 출력되는 것을 확인할 수 있다.

스크린샷 2015-05-11 오전 12.28.16

이는 goroutine이 각각 Thread와 같이 독립적으로 실행이 된다는 것을 보여준다. 그럼에도 불구하고 goroutine은 Concurrency이다. 그러므로 자원을 Locking/Unlocking하는 코드들이 불필요하다. 아랫단의 일들을 golang의 runtime이 알아서 해주고 Thread를 여러개 실행 시키도록 셋팅하면 각 thread에 goroutine을 할당하여 자원을 관리하는 일까지도 언어단에서 알아서 해준다. 개발자들은 C/C++에서 thread를 사용하는 것과 마찬가지로 적절한 작업들에 goroutine을 호출시키고 channel을 이용해서 결과값들을 받아서 훨씬 수월하게 처리할 수 있다.

이러한 언어의 Concurrency는 기존에 C나 C++에서 다루었었던 멀티쓰레드와 자원 관리의 복잡함으로부터 개발자들을 해방시키는 역할을 해준다. 동시에 Multi-Thread로 해냈던 수준의 Async하고 parallel한 작업들을 흉내내거나 손쉽게 수행할 수 있도록 해준다.

Concurrency와 Parallelism의 특성에 관해 좀더 알기 위해선 Golang의 creator중에 한사람인 Rob Pike의 talk가 참고할 만하다.

Rob Pike – ‘Concurrency Is Not Parallelism’ from Waza on Vimeo.