Golang 언어에 대한 포스터를 작성하고자 합니다. ‘Tucker의 Go 언어 프로그래밍’이란 책을 통해 학습했던 내용을 정리하고자 합니다. 정리할 내용은 변수 간 값의 전달, 스택과 힙, 고루틴과 컨텍스트, 채널, 클래스와 상속입니다. 위의 내용에 대해 쓸려고 하는 이유는 Golang 만의 다른 점이 있다고 생각했기 때문입니다. 위의 내용 말고도 Golang 언어의 특징은 다음과 같습니다. 컴파일 언어, 강 타입 언어이면서 기본적으로 라이브러리를 모두 정적 링크합니다. 그래서, DLL의 의존 문제를 회피할 수 있고, 런타임 실행 시간도 빠릅니다. 하지만, 정적 링크하기 때문에 파일 크기가 크고, 라이브러리의 수정이 생기면 다시 컴파일 해줘야 한다는 불편함도 있습니다.


변수 간 값의 전달


Go 언어에서 변수 간 값의 전달은 타입에 상관없이 항상 복사로 일어납니다. 대입 연사자와 함수 호출 시 인수값 전달에도 항상 복사로 전달됩니다. 값 전달은 우변의 메모리 공간을 좌변의 메모리 공간에 복사함으로써 일어납니다. 복사하는 크기는 타입의 크기와 같습니다.

예를 들어, Go 언어에서 배열 간 대입 및 인수 전달은 다음과 같이 일어납니다. 우변의 타입크기*배열크기 만큼 좌변 메모리 공간에 복사합니다. 흔히, 깊은 복사가 일어납니다. 구조체의 경우에도 같습니다. 구조체 내부에 정의한 필드의 총 타입 크기 만큼(메모리 정렬) 복사가 일어납니다.

추가적으로 슬라이스, 스트링, 맵, 채널 간 대입 및 인수 전달 시 참조형 변수처럼 작동하는 것처럼 보이지만, 사실은 내부적으로는 구조체로 구현되어 있으며 실제 데이터를 가리키는 포인터 필드가 있고 복사 시 우변의 총 필드 메모리 공간만큼 좌변 메모리 공간에 복사합니다. 그래서, 기본값으로 nil을 가리키며 대입 및 인수 전달 시 얕은 복사가 일어나는 것처럼 보입니다.

그리고, 함수 타입은 포인터와 마찬가지로 기본 타입이며 값으로 메모리 주소를 가집니다.


스택과 힙


대부분의 프로그래밍 언어는 메모리를 할당할 때 스택 메모리 영역 또는 힙 메모리 영역을 사용하고, C/C++ 에서는 malloc() 함수를 통해 힙 메모리 공간을 할당하고, 자바에서는 클래스 타입을 힙에, 기본 타입을 스택에 할당합니다. Go 언어에서는 탈출 검사(escape analysis)를 해서 어느 메모리에 할당할지를 결정합니다. 그래서, Go 언어에서는 메인 함수가 아닌 다른 함수에서 객체를 생성해도 메인 함수에서 해당 객체를 사용한다면 스택이 아닌 힙 메모리에 객체를 할당하게 됩니다. 이러한 작동 방식은 생성자 기능이 없는 Go에서 구조체의 디폴트 생성자와 같은 역할을 하는 함수 사용을 가능하게 합니다.


고루틴


고루틴은 다른 언어에서 사용하는 쓰레드와 비슷하지만, 더 가볍고 효율적인 동시성을 제공합니다. 고루틴은 사용자 수준에서 관리되기 때문에 작은 메모리 오버헤드로 생성되며, 고루틴 간의 컨텍스트 스위칭 비용이 전통적인 OS 쓰레드보다 적습니다.

일반적인 쓰레드에서는 CPU 코어당 한 개의 프로세스(또는 하이퍼스레딩을 사용할 경우 두 개의 프로세스)를 처리할 수 있으며, 여러 쓰레드 간의 컨텍스트 스위칭이 발생할 때 성능 저하가 발생할 수 있습니다. 그러나 고루틴은 이러한 컨텍스트 스위칭의 부하를 최소화하고, 개발자가 직접 코어를 관리할 필요 없이 고루틴의 스케줄링이 내부적으로 효율적으로 이루어집니다.

고루틴이 필요에 따라 OS 스레드에 분배되어 실행되며, 이 과정은 Go 런타임이 자동으로 관리합니다. 따라서 고루틴이 컨텍스트 스위칭 비용을 거의 발생시키지 않으며, 매우 효율적으로 동시성을 처리할 수 있습니다.


채널과 컨텍스트


채널과 컨텍스트는 고루틴의 동시성 프로그래밍을 도와주는 역할을 합니다. 채널은 고루틴끼리 메시지를 전달할 수 있는 메시지 큐입니다. 흔히 동시성(다중 처리) 프로그래밍을 할 시 공유된 자원을 사용하는 경우 자원에 모순성이 생기는 문제가 발생할 수 있습니다. 이러한 문제를 해결하기 위해서는 임계 영역, 이벤트 객체, 뮤텍스 등의 방법을 사용해 동기화 작업을 해주어야 합니다. 하지만, 동기화 작업을 할 시 성능이 떨어질 수 있기에 Go 언에서는 채널과 컨텍스트를 사용해 공유된 자원에 역할을 나누는 방법을 사용합니다.


클래스와 상속


Go 언어에서는 클래스와 상속을 지원하지 않습니다. 대신 인터페이스와 구조체를 사용해서 객체 지향 프로그래밍을 지원합니다. 인터페이스는 메서드 시그니처만 정의하며, Go는 이를 통해 덕 타이핑(duck typing)을 구현합니다. 이는 객체의 타입보다는 객체가 수행할 수 있는 동작(메서드)에 초점을 맞추어 유연성을 제공합니다. 구조체는 필드와 메서드를 포함할 수 있으며, 인터페이스를 통해 다양한 동작을 구현할 수 있습니다. 이러한 특징은 객체 간 상속 관계가 아닌 포함 관계를 사용하도록 합니다. 상속 관계가 아닌 포함 관계를 사용함으로써 구체화된 객체는 추상 객체에 의존(DIP)하기 쉬워집니다.