허니몬의 IT 이야기
병렬(Concurrent) 프로그래밍 언어 : Erlang
현재 주로 사용되는 프로그래밍 언어 C, C++, 자바 등의 프로그래밍 모델은 병렬 컴퓨팅이나 분산 시스템(Distributed
system)을 이론적으로 설명하는 수학적 모델과 상당한 격차가 있다. 이런 격차는 이론과 현실의 괴리를 가져왔고 잘
정립된 이론적 바탕을 두고도 개발자들은 뮤텍스와 컨디션 변수를 가지고 씨름하는 현실에 생기게 된 것이다.
이런 이론과 현실과의 괴리를 좁히고 병렬 프로그래밍을 전혀 다른 방식에서 접근한 언어로 프로그래밍 언어 Erlang
이 있다. Erlang은 1982년 이동통신 회사인 에릭슨(Ericsson)이 프로그래밍 언어에서 병렬 수행 지원을 가장 큰 목표로
두고 개발한 언어이다. 일련의 연구를 통해 Erlang 팀은 2가지 결론을 내렸다.
(1) 리스프(Lisp)나 프롤로그(Prolog) 같은 심볼릭(Symbolic) 언어가 훨씬 생산적이다.
(2) 초경량 프로세스(Ultra-lightweight process), 적극적 오류 회복(Activeerror recovery), 분산 메시지 전달(Distributed message passing) 같은 병렬 수행의 개념을 갖춘 언어가 병렬 프로그래밍에 훨씬 유리하다.
하지만 당시에 이런 조건을 모두 갖춘 언어가 없었으므로 새로운 언어를 디자인했는데, 그 결과로 나온 것이 바로 Erlang이다.
프로그래밍 언어를 연구하는 사람들은 함수형(Functional)언어가 병렬성 지원에 이상적이라고 말을 한다. 왜냐하면
병렬성 이론을 설명하는 데 일반적으로 수학적 함수를 많이 사용하고, 함수형 언어는 이런 수학적 함수를 직관적으로
구현하기 쉽기 때문이다. 하무형 언어는 명령형(imperative) 언어처럼 문(statement)을 실행하는 개념이 아니라 표현식
(Expression)을 계산(evaluation)하는 방식으로 프로그램을 수행한다. 덕분에 익숙하지 않은 사람은 함수형 언어가
어색하게 느껴질 수도 있지만, 이런 언어들은 매우 안정적임이 이론적으로 증명되어 있다.
Erlang은 함수형 언어로 설계되었다. 이해를 돕기 위해 피보나치 수열을 계산하는 간단한 예제를 살펴보자.

< Erlang으로 작성한 피보나치 수열>
fib(1) -> 1;
fib(2) - > 1;
fib(X) - > fib(x-1) + fib(x-2).

fib(5)를 호출하면 위에서 정의한 세가지 함수 fib(1), fib(2), fib(N) 중에 알맞은 함수를 패턴 매칭(Pattern matching)으로
찾아서 부른다. 5는 1, 2가 아니므로 fib(N)이 불린다. fib(5)는 fib(4)+fib(3)이 되고 fib(4)는 다시 fib(3)+fib(2), fib(3)은
fib(2)+fib(1)이 된다. fib(2)와 fib(1)은 1이라는 값을 가지므로 여기서 계산이 끝나게 된다. 즉 C/C++의 재귀함수(Recursive
function)가 마찬가지로 계산한다.
Erlang의 기본적인 데이터 타입은 다른 함수형 언어와 마찬가지로 튜플(tuple)과 리스트(list)이다. 더불어 아톰(atom)은
심볼, 상수(constant), 숫자, 문자열처럼 하나의 값을 말한다. 다음은 Erlang 데이터 타입의 예젱다.


atom
16
4.6

[this, is, a, ist, that, can, go, on, and, on]

{a, tuple}
{
{{word, tuple},
{definition, "a fixed length ordered collection of atoms"}},
{{world, erlang},
{definition, "a functional language optimized for concurrency"}}
}


sum([H|T]) -> sum(T) + H;

Erlang에서 리스트는 보통 패턴 매칭(Pattern matching)으로 처리한다. sum([H|T])에서 H는 머리(Head)로 리스소의
첫 번째 원소를 뜻하고 T는 꼬리(Tail)로 첫번째 원소를 제외한 리스트의 나머지를 말한다. 앞의 함수는 첫 번째
원소(H)의 값을 나머지 리스트(T의 전체합과 더하는 방식으로 전체 리스트[H|T]의 합을 계산한다. 이런 재귀 호출
방식의 계산은 함수형 언어의 전형적인 특징이다.
Erlang 언어 자체에 깊이 있는 내용은 관심있는 독자 분들은 좀 더 찾아보기를 권하며 Erlang의 병렬 프로그래밍
지원에 대한이야기로넘어가겠다. Erlang은스레드라는용어 대신에 경량프로세스(lightweight process)라는개념을
사용한다. Erlang에서 Pid=spawn(math, fact, [1234])와 같이 주면 매우 큰 팩토리얼(factorial)을 계산하는 프로세스를
생성한다.
Pid는 프로세스 간에 메시지를 주고받는 경로로 사용된다. Pid ! {hello, world}와 같이 주면 ! 연산자는 해당 Pid의
프로세스에 {hello, world} 라는 튜플을 메시지로 전달하라 의미이다. 받는 쪽에서 다음 코드처럼 receive 키워드를 통해
이 메시지를 받을 수 있다. 여기서도 함수형 언어의 전통을 따라 패턴 매칭을 사용하였다.


loop() ->
receive
{From, message} ->
io:format("Received : ~w ~n", [Message]),
loop();
stop ->
true
end.

여기서 다시 Erlang이 스레드가 아닌 프로세스라는 용어를 사용하고 있다는 점이 주목할 필요가 있다. Erlang이 말하는
경량 프로세스는 단순히 스레드를 지칭하는 또 다른 용어가 아니라 OS에서 말하는 프로세스에 가까운 특징을 가지고
있다. OS 프로세스처럼 Erlang 경량 프로세스는 서로 메모리 영역을 전혀 공유하지 않는다.
따라서 자바 멀티스레드 프로그래밍처럼 공유자원을 여러 스레드가 동시에 접근하는 일이 일어날 수 없다. 모든 데이터는
!와 receive를 통해서
명시적으로 주고받아야 한다. 덕분에 자바 프로그램에 빈번히 데드락과 레이스 컨디션 같은 상당수의 멀티스레드 버그가 Erlang에서는 이론적으로 발생할 수 없게 된다.
Erlang의 또하나의 특징은 단일배정(Single assignment)에 있다. 단일배정은 변수가 한번 초기화되고 나면 다른값으로
변화할 수 없는 수정 불가능한(immutable) 값이 됨을 의미한다. 수정 불가능한 타입은 값을 읽을 수만 있고 쓸 수는
없으므로 상당수의 병렬 수행 버그를 없앨 수 있다. 실제로 자바 멀티스레드 프로그래밍에서도 수정 불가능한 타입의
사용은 중요한 팁의 하나인데, Erlang은권고 수준이 아니라 언어 차원에서 이를 강제하고있다.
이처럼 Erlang의 병렬 수행 지원에 대해서 간략히 살펴보았는데, 여러가지 장점에도 불구하고 함수형 언어인 Erlang이
짧은 기간 안에 주류 언어로 부상하리라고 기대하지는 않는다. 다만 Erlang이 주는 교훈은 자바의 멀티스레드 방식이
아니더라도, 병렬 프로그래밍을 쉽게할 수 있는 간단한 패러다임이 있을 수 있다는 점이다. 주류 언어에 익숙한 개발자는
멀티스레드 프로그래밍이 유일한 병렬 프로그래밍이라고 생각하기 쉽다. 하지만 멀티 코어 시대에 사는 소프트웨어
개발자는 이제 좀 더 생산적인 패러다임을 생각해야 한다.