GO언어란?
Go 언어는 빠른 성능, 안정성, 편의성, 쉬운 프로그래밍을 목표로 개발되었으며 범용 프로그래밍 언어입니다.
내용
구글이 2009년에 만든 프로그래밍 언어입니다. 이름이 이름인 만큼 검색이 불편해 보통 golang이라고 많이 이야기합니다. Go 언어의 사용자들은 고퍼(Gopher)라고 부르며, 고퍼들을 위한 연례행사인 고퍼콘(Gophercon)이 세계 각국에서 매년 열리고 있습니다. 전반적으로 C에서 영향을 많이 받았으며, 컨테이너 기반 가상화 도구인 Docker를 개발하는데에 사용된 언어입니다. 또한 Go 언어를 이용해서 안드로이드와 iOS 앱을 제작할 수 있게 적용 중입니다. 또한 Python이 그렇듯 웹 개발에도 쓰이고 있고 특히 서버 사이드 언어로서 좋은 평가를 받고 있습니다. 또한, Go가 자체적으로 지원하는 라이브러리만으로도 간결하게 웹사이트를 만들어낼 수 있습니다.
Go 언어의 특징은 컴파일 언어이지만 컴파일러의 컴파일 속도가 매우 빨라 인터프리터 언어처럼 쓸 수 있다는 점에 있습니다. 이는 언어의 문법 구조를 개선함으로써 달성하였습니다. 컴파일러가 소스 코드를 해석하는 pass 수를 줄여서 달성한 것으로 보입니다. 접근하기 어렵지 않고, 코드 역시 간결하면서도 컴파일 언어답게 높은 성능을 낼 수 있다는 점이 호평을 받습니다. 간결하게 코드를 작성할 수 있으면서도 풍부한 라이브러리 등의 덕택에 막강한 기능을 쉽게 구현할 수 있다는 것은 큰 장점이다.
그러나 컴파일 언어의 태생적인 한계를 극복한 것은 아니라서 대형 모듈을 이것저것 붙이면 컴파일에 필요한 시간이 있기에 Python 등의 인터프리터 언어보다는 기동할 때에 확실히 반응이 늦습니다. 물론 컴파일 언어 중에서는 매우 빠른 편이지만 아무래도 인터프리터 언어의 즉응성까지 바라는 건 무리지만 물론 컴파일 언어인만큼 실행 시 퍼포먼스는 확실합니다.
또한 Go는 GoRoutine이라는 비동기 메커니즘을 제공합니다. 이 비동기 메커니즘은 Erlang에서 영향을 받은 것으로 각각의 고루틴은 병렬로 동작하며 메시지 채널을 통해 값을 주고받습니다. 고루틴을 사용하면 이벤트 처리, 병렬 프로그래밍 등이 간단해집니다. 단 병렬화된 고루틴의 동기화 문제는 프로그래머가 챙겨 줘야 하며 동기화를 무시할 경우 프로그램이 비정상 종료할 수도 있습니다. 예를 들어 부모 루틴이 자식 루틴보다 먼저 끝나버리면 자식 루틴은 OS에 의해 메모리에서 강제로 사출되어 버립니다. 단 동기화 방법은 기존 멀티스레드 응용프로그램에 비해 매우 간단한 편이고 단순히 고루틴으로부터 반환값을 받는 줄을 메인 스레드에 추가하면 됩니다.
고루틴은 멀티스레드 메커니즘이지만 자체적인 스케줄러에 의해 관리되는 경량 스레드이며 OS에서 관리하는 경량스레드보다 더 경량입니다. 따라서 고루틴은 CPU 코어수와 무관하게 수백 수천만 고루틴을 작성해도 성능에 문제가 생기지 않습니다.
다만 컴파일 언어이므로 바이너리만 배포할 경우 해당 타깃 머신에 맞춰서 컴파일해야 합니다. 바이트코드를 생성하는 방식이 아니므로 다중 플랫폼을 지원하려면 소스코드 통째로 배포해야 합니다.
특징
GO언어의 특징은 다음과 같습니다.
- 정적 타입, 강 타입
- 컴파일 언어
- 가비지 컬렉션
- 병행성(Concurrency)
- 멀티코어 환경 지원
- 모듈화 및 패키지 시스템
- 빠른 컴파일 속도
- Go 언어의 문법은 C 언어를 기반으로 하고 있으며, C++의 복잡한 문법 대신 간단하고 간결한 문법을 추구하고 있습니다.
정적 타입과 동적 타입
프로그래밍 언어에서 타입은 우리말로 자료형을 뜻합니다. 그리고 자료형에는 보통 정수, 실수, 불, 문자열, 객체 등이 있습니다. 여기서 자료형을 컴파일할 때(Compile-time) 결정하면 정적 타입이고, 실행할 때(Run-time) 결정하면 동적 타입이라 합니다. 요즘 흔히 사용하는 Python, Ruby, JavaScript 등의 스크립트 언어는 동적 타입(Dynamic Typing) 언어입니다.
# python
val = 10 val2 = 'test' # 타입이 없음
반면 C, C++, Java, C# 등의 언어는 정적 타입(Static Typing) 언어입니다. 이 언어들은 값의 형태에 따라 각각 자료형을 가지며 자료형에 맞지 않는 값을 대입하면 컴파일할 때 에러가 발생합니다.
int val = 10; char val2 = 'a';
약 타입과 강 타입
약 타입(Weakly-typed)은 값의 타입을 바꿀 수 있습니다. 여기서 값의 타입 바꾸기를 형 변환이라고 합니다. 즉 약 타입 언어는 자료형이 달라도 컴파일 또는 실행 시점에 정해진 규칙에 따라 암시적 형 변환(Implicit conversion)을 해주는 방식입니다. 다음과 같이 C 언어는 약 타입 언어이므로 자료형이 달라도 암시적 형 변환을 합니다.
int a = 1; float b =1.3; float c = a + b; // int인 a가 float으로 변경
강 타입(Strongly-typed)은 값 자체가 타입이며, 타입을 바꿀 수 없습니다. 즉 컴파일 또는 실행할 때 자료형이 다르면 에러를 발생시킵니다. 다음과 같이 Go 언어는 강 타입 언어이므로 암시적 형 변환을 하지 않습니다. 또한, 컴파일할 때 타입을 결정하므로 정적 타입 언어입니다.
var a int = 1 var b float32 = 1.3 var c float32 = a + b // int형인 a가 float형으로 변환되지 않아 컴파일 에러발생
가비지 컬렉션
Go 언어는 가비지 컬렉션(Garbage Collection, GC)을 제공합니다. C, C++는 메모리를 할당하면 반드시 해제를 해주어야 합니다. 이렇게 되다 보니 C, C++ 프로그래밍은 로직 작성보다 메모리 관리에 더 많은 노력과 시간을 소모하고 있어서 생산성이 많이 떨어집니다. 이후 메모리를 알아서 관리해주는 가비지 컬렉션 기술이 나왔고, 이 기술을 사용한 Java와 C#이 등장했습니다. 마찬가지로 Python, Ruby, JavaScript 등의 스크립트 언어들도 각 언어의 가상 머신에서 가비지 컬렉션 기술을 사용하고 있습니다.
아래의 그림과 같이 Go 언어는 메모리를 관리해주는 가비지 컬렉터(Garbage Collector)가 실행 파일안에 내장되어 있습니다. 가상 머신 위에서 실행되는 언어들처럼 가상 머신이 메모리 관리를 해주는 것과 차이가 있습니다. 즉 Go 언어는 C, C++ 실행 파일 방식의 간결함과 가상 머신의 가비지 컬렉션 기능을 함께 가지고 있습니다.
Go 언어는 메모리 관리에 신경 쓰지 않고, 로직 작성에 집중할 수 있습니다. 따라서 스크립트 언어(가상 머신 위에서 실행되는 언어)처럼 생산성이 높고 C, C++처럼 빠른 성능이 장점입니다.
병행성
Go 언어는 언어 차원에서 병행성(Concurrency, 동시성)을 제공합니다. 먼저 병행성과 병렬성(Parallelism)의 차이를 알아보겠습니다.
- 병행성: 동시 처리의 논리적인 개념입니다. 단일 코어에서 스레드를 여러 개 생성하면 겉으로 보기에는 동시에 실행되는 것처럼 보이지만 실제로는 스레드 여러 개가 시간을 쪼개어 순차적(시분할)으로 실행됩니다. 물론 논리적인 개념이기 때문에 단일 코어에서 처리되든 멀티코어에서 처리되든 병행성은 만족합니다.
- 병렬성: 동시 처리의 물리적인 개념입니다. 작업을 여러 CPU 코어에 나눠서 동시에 처리하는 상태를 뜻합니다.
Go 언어는 go 키워드를 통해 함수 여러 개를 동시에 실행할 수 있습니다. 이렇게 실행된 함수를 고루틴(Goroutine)이라고 하는데 스레드와는 차이점이 있습니다. 스레드는 운영체제의 커널에서 제공하는 리소스이기 때문에 많이 생성할수록 부담이 커집니다. 그림 1-5와 같이 Go 언어는 적정량의 스레드를 생성해서 고루틴을 처리합니다. 또한, 최대 프로세서(코어) 개수 설정에 따라 멀티코어도 지원됩니다(현재 프로세서의 개수를 구할 수 있으며, 일부 프로세서만 사용할 수도 있습니다).
아래 그림과 같이 Go 언어에서는 채널을 이용하여 고루틴끼리 통신을 할 수 있습니다. 즉 간단하게 채널을 통해 데이터를 공유하고 실행 순서를 제어합니다. 지금까지 해오던 멀티스레드 프로그래밍의 동기화 객체와 비슷합니다. Go 언어의 개발자인 롭 파이크는 1980년 대부터 병행성을 지원하는 프로그래밍 언어를 연구해 왔으며 CSP를 따르는 Newsqueak 언어를 개발했고, Go 언어에 채널 개념을 도입했습니다.
모듈화 및 패키지
예전과는 다르게 지금은 소프트웨어의 규모가 매우 크고 복잡해져서 코드의 재사용성이 더욱 중요해졌습니다. 최근에 나온 프로그래밍 언어는 객체지향 개념이 도입되었고, 패키지 시스템까지 지원해주고 있어서 중복 개발이 많이 줄었습니다.
Python pip, Ruby gem, Node.js npm, Java Maven 등의 패키지 시스템은 인터넷의 저장소에서 패키지를 받아오고, 의존성까지 자동으로 관리해줍니다. 마찬가지로 Go 언어는 언어 자체에서 모듈화를 제공하며 인터넷에 있는 소스 코드를 바로 가져와서 사용할 수 있습니다. 그리고 다양한 패키지 관리 도구로 패키지간 의존성을 쉽게 관리할 수 있습니다.
import 키워드로 저장소 주소만 지정한 뒤 go get, go install 명령을 사용하면 Git, Mercurial, Subversion, Bazaar로 GitHub, BitBucket, Launchpad, IBM DevOps Services에서 자동으로 소스 코드를 가져옵니다.
컴파일 속도
C, C++은 컴파일할 때 처리해야 되는 헤더 파일이 많아서 컴파일 속도가 매우 느립니다. 게다가 헤더 파일 간의 의존관계가 매우 복잡하여 헤더 파일이 조금만 수정되어도 컴파일을 다시 하는 문제가 있습니다. 특히나 요즘은 프로그램의 규모가 더 커졌기 때문에 컴파일 시간이 더 길어지고 있습니다.
반면에 Go 언어는 C, C++과는 달리 헤더 파일이 없고, 소스 코드를 패키지화하여 변경된 부분만 컴파일하므로 컴파일 속도가 빠릅니다. 또한, 문법적으로도 복잡한 요소를 최대한 줄여 컴파일 속도에 유리하게 설계했습니다.
언어의 문법도 생산성에 큰 영향을 미치지만 컴파일 속도도 매우 중요합니다. 따라서 Go 언어는 코드를 간결하게 표현할 수 있으면서 컴파일 속도가 빠르므로 다른 컴파일 언어에 비해 생산성이 훨씬 높습니다.
활용범위
Go 언어는 웹 브라우저, 서버, 데이터베이스 등 규모가 크고 복잡한 애플리케이션을 개발하는데 적합합니다. 이러한 분야는 이제 메모리 관리에 시간을 쏟기 보다는 로직에 집중하는 것이 중요해졌습니다. 따라서 메모리를 일일이 신경 쓰지 않아도 되는 Go 언어로 작성하면 생산성을 높일 수 있습니다. 하지만 아래 그림과 같이 Go 언어는 메모리 관리를 철저히 해야 하는 시스템 라이브러리 개발에는 적합하지 않습니다. 그리고 메모리 및 장치에 직접 접근해야 하는 운영체제와 장치 드라이버도 개발하기 힘듭니다.
요약하자면 Go 언어는 메모리 관리가 다소 느슨해도 되고, 규모가 크고 복잡하며 유지보수가 빈번한 곳에서 편리하게 사용할 수 있습니다. 그리고 다양한 네트워크 라이브러리(패키지)를 제공하므로 인터넷 프로그래밍에 유용합니다.
참고
컴파일 언어인 덕분에, 속도가 느린 스크립트 언어에서 연산 퍼포먼스가 필요한 부분을 Go로 대체해 넣을 수도 있습니다. 예를 들면, Go로 만든 코드를 공유 라이브러리로 컴파일해 Ruby에서 FFI를 이용해 컴파일한 .so 파일을 가져와 사용하는 식이며 PHP에서도 역시 가능합니다. 반대로 Go에서 만든 라이브러리를 스크립트에서 사용할 수 있습니다. PHP를 예로 들면, libphp5.so를 이용해 Go에서 PHP 코드를 불러와 실행하는 행동을 할 수 있습니다. 다만 기본 패키지들 중에서는 성능보다는 편의성에 초점을 맞춘 탓에 극한의 성능을 추구하는 경우라면 사용을 권하기 어려운 것들이 있습니다. 예를 들면 웹 서버 제작시에 쓰이는 net/http나 html/template 등이 그러한데, 이런 경우엔 기본 패키지를 대체하는 별도의 패키지를 이용하면 벤치마크상으로는 심지어 수십 배나 수치가 좋아지는(...) 경우도 있습니다. 예를 들어 html/template의 경우 성능 저하의 주범으로 여겨지는 reflect를 사용해서 템플릿을 생성할 때 (코드 작성자 입장에서는 할 일이 줄어서 편리하지만) 자동으로 모두 이스케이프 처리를 해서 렌더링을 하므로 속도가 느려지게 됩니다. 당연하지만 이런 대체 패키지들을 쓰게 되면 대신 코드 작성은 조금 귀찮아지게 되고, 또한 기본 패키지의 문법이 호환되지 않는 경우도 잦습니다. 다만, 실제 서비스를 구성할 때에는 통상적으로 템플릿 캐싱을 추가할 것이므로 느린 파싱 속도는 사실 문제가 되지 않습니다. net/http의 경우에도 Nginx 못지 않은 성능을 보여주는 대체 패키지가 존재한다. 이런 물건들을 사용한 몇몇 프레임워크들은 유독 벤치마크 그래프가 하늘을 뚫을 기세로 눈에 띕니다(...).
Go 언어의 설계 지향점이 시스템 프로그래밍 언어이었으나, 가비지 컬렉션의 지원이나 제네릭의 부재로 인해 불필요하게 많이 사용되는 박싱/언박싱 등으로 인해 C/C++을 대체할 수 있는 언어는 아니라는 것에 합의가 이루어지고 있는 상황입니다. 실제로 고성능 연산에 사용하기에는 C/C++에 비해 너무 느리며, 저수준 시스템 개발에서는 가비지 컬렉션과 고루틴을 지원하기 위한 무거운 런타임 등으로 인해 사용이 불가능에 가깝습니다. 그런 이유로 대체로 개발 속도와 실행 속도, 병행성 사이의 적정 지점이 필요한 서버 애플리케이션 개발 등에 많이 사용되는 편입니다.