Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>
<!> 미완성 여기에서는 개요만 살펴볼 뿐이다. 자세한 내용은 "참고"에 링크해둔 문서들을 참고하자.

소개

Golang이라고 부르기도 하는 Go는 2007년 Robert Griesemer, Rob Pike, Ken Thompson에 의해서 개발된 프로그래밍 언어다. Go는 C와 매우 유사한 형식을 가지는데, C 보다 느슨한 문법을 제공하는 정적타입언어로, 가비지 컬렉션, 가변형 배열, 키-값 기반의 map 등의 고수준 자료구조와 다양한 표준 라이브러리를 포함하고 있다.

Go는 2009년 11월 발표했으며, 구글 제품 개발을 위해서 사용하고 있다. Go는 리눅스, Mac OS X, FreeBSD, NetBSD, OpenBSD, Plan 9, 마이크로 소프트 윈도우즈 운영체제와 i386, amd64, ARM, IBM POWER 프로세서 아키텍처를 위한 컴파일러를 제공한다. 기타 GCC의 프론트엔드인 gccgo 컴파일러도 제공한다.

언어 디자인

Go는 C와 유사한 측면이 있지만 좀더 안전하고 간결하고 단순함을 추구하고 있다. 아래 Go언어의 기능을 간단히 설명한다.
  • 다이나믹 언어의 문법 패턴을 따르고 있다. 사용하기 쉽다는 이야기.
    • int x = 0 대신 x := 0과 같은 좀더 간결한 변수 선언과 초기화
    • 빠른 컴파일
    • go get을 이용한 원격 문서/패키지 관리 시스템
  • 특정 문제에 대한 독특한(효율적인) 접근 방식
    • Concurrency primitive의 내장 : 경량 프로세스(goroutines), channel, select를 이용한 간단한 동시성 접근
    • Virtual inheritance를 대신하는 인터페이스(interface) 시스템
    • 외부 의존성없이 네이티브 바이너리를 정적으로 링크하는 툴체인
  • 프로그래머 개인의 머리에 담을 수 있을 정도의 간단한 언어 사양.
  • 객체지향적 : 클래스가 없지만, 객체지향 적으로 프로그래밍 할 수 있다.
  • 가비지 콜랙션 : 일반적으로 가비지 컬렉션은 비용이 많이드는 것으로 알려져 있지만, (C나 C++)프로그래밍에서 메모리 관리를 위해서 투입하는 비용을 생각해 보면 가비지 컬랙션은 이득이 더 많다.
  • 동시성 : 고언어는 CSP를 기반으로 동시성을 제어 한다. 동시성 과 멀티 스레드는 오래된 기술이라서 뭐 특이할 만한게 있을까 ? 라고 생각 할 수도 있다. 고루틴과 채널을 사용하면, 신세계를 경험할 수 있다.

문법

Go의 문법은 C와 유사하다. C언어에 대한 지식을 가지고 있다면, 쉽게 읽을 수 있다. C 언어의 경우 int i = 3, char *s ="somewords"와 같이 타입을 알려줘야 하지만 go는 i :=3, s := "someworld" 로 타입을 알려줄 필요가 없다. 또한 줄 마지막에 세미콜론(;)을 넣을 필요도 없다

C언어는 하나의 값만을 리턴할 수 있다. 에러 상태도 값으로 알려줘야 하는데, 코드의 해석과 사용을 어렵게 한다. 예컨데, -1이 에러일 수 있지만 어떤 경우에는 정상적인 값일 수 있다. Go는 result, err와 같이 두 개의 값을 반환 할 수 있다.
value, err := net.ResolveUDPAddr("udp4", service) 
if err != nil {
	// 에러처리 ...
}
Go 문법의 주요 특징들이다.
  • 간단하며, 빠르게 배울 수 있다.
  • 키워드가 적다.
  • 2개 이상의 값을 반환 할 수 있다.(튜플이 아니다. 말 그대로 두 개의 값을 반환한다.)
  • Public / Private를 지원한다. 루비처럼 대소문자로 구분한다. 첫 문자가 대문자면 public, 소문자면 private이다.
  • Map, Slice(vector), queues의 지원
  • 포인터 : 포인터 연산은 없다.
  • 없는 것들
    • 매크로
    • 예외(exceptions)
    • (제너릭 프로그래밍을 위한) template : 향후 지원 할 수도 있을 거라고 한다.
    • 연산자 및 메서드 오버로딩

Hello World

package main
import "fmt"

func main() {
    fmt.Println("Hello World!")
}
Hello World 프로그램이다. 전반적으로 C 코드와 같은 느낌을 받을 수 있다. 패키지 시스템이 눈에 띈다.

package main

import "fmt"

func main() {
	a, b := 1, 2
	fmt.Printf("a+b = %d\n", a+b)
	for a < 100 {
		a++
		fmt.Println(a)
	}
}
:= 를 이용하면 컴파일 시간에 타입을 자동으로 알아낸다. for 문도 더 단순하다.

Import, casting

package main

import (
	"fmt"
	"math"
)

func main() {
	for i := 0; i < 100; i++ {
		fmt.Println(math.Pow(float64(i), 2))
	}
}

함수

package main

import (
	"fmt"
)

func square(i int) int {
	return i * i
}

func main() {
	for i := 0; i < 100; i++ {
		fmt.Println(i, square(i))
	}
}
리턴 값의 위치에 주목하자. C 언어의 경우 리턴 값이 함수의 가장 앞에 오지만, Go는 뒤에 온다.

Struct와 Methods

package main

import (
	"fmt"
)

type User struct {
	Name     string
	password string
}

// User를 위한 메서드
func (u *User) Authenticate(name, password string) bool {
	return u.Name == name && u.password == password
}

// 생성자 역할을 하는 함수
func NewUser(name, password string) *User {
	return &User{Name: name, password: password}
}

func main() {
	user := NewUser("yundream", "12345")
	if user.Authenticate("yundream", "1234") {
		fmt.Println("Authenticate success")
	} else {
		fmt.Println("Authenticate Fail")
	}
}
클래스는 없지만 struct와 메서드를 이용해서 객체지향 프로그램을 개발 할 수 있다. Name은 Public, password는 private다. Private 변수의 경우 로컬 패키지에서만 사용 할 수 있다.

성능

Go는 빠르다. 일반적으로 C++보다 약간 더 느린 것으로 알려져있다. Java와 비슷하며, Python이나 Ruby 보다는 10 ~ 100배 정도 빠르다. C 만큼 빠른 프로그램의 개발은 Go 언어의 주요 목적 중 하나다. Go는 매우 빠르게 버전업 되고 있는데, 그 때마다 성능도 함께 향상되고 있다. 여기에서 언어별 벤치마크 결과를 볼 수 있다.

동시성

Go는 동시성(concurrency)을 지향한다. 동시성과 병렬성(parallelism)의 차이점을 살펴봐야 할 것 같다.
  • 동시성은 독립적으로 실행되는 프로세스들의 조합이다.
  • 병렬성은 동시에 실행 되는 컴퓨팅 연산을 의미한다.
실제 생활을 예로 들어서 설명해 보자.

나는 오늘 보고서를 만들어야 하고, 코딩도 해야 하고, 회의에도 참석해야 한다. 보고서를 만들다가 지겨우면 코딩을 하고, 그러다가 회의에 참석 하고 다시 보고서를 만드는 식으로 혼자서 3가지의 독립적인 업무를 함께 처리 할 수 있다. Concurrency한 작업 방식이다.

보고서는 김대리에게, 회의는 이대리에게 맡기고 나는 코딩을 하는 방법도 있다. 3명이 동시에 업무를 진행하게 하는데, Parallelism한 업무처리 방식이라고 할 수 있다.

그러니까 동시성은 소프트웨어의 운용에 대한 거고, 병렬성은 하드웨어 운용에 대한 거라고 말 할 수 있다. 병렬성을 위해서는 반드시 두 개 이상의 코어가 필요함을 미루어 짐작할 수 있다. 동시성은 시분할을 이용해서 (내가 시간을 쪼개서 3개의 업무를 처리하는 것 처럼)하나의 코어에서도 가능하다.

좀 더 자세히 정리해 보자.
  • 동시성은 여러 개의 문제를 다루는 것에 대한 거다.
  • 병렬성은 한번에 여러 개의 문제를 실행하는 방식이다.
  • 같지는 않지만 서로 관련되 있다. (다른 말로 헷갈린다는 의미다.)
  • 동시성은 구조에 대한 것이고, 병렬성은 실행에 대한 거다.

동시성에서의 커뮤니케이션

동시성은 독립된 여러 개의 코드의 실행을 관리한다. 이는 독립된 코드간의 통신이 필요 할 수 있음을 의미한다. Go는 (Erlang도 이용하는)CSP(Communicating Sequential Processes) 모델을 이용하고 있다.

고루틴

동시성은 Go의 핵심기능으로 고루틴(goroutine)와 채널(channel), select를 이용해서 동시성을 구현한다. 고루틴은 내부 스케줄러를 이용해서 작동하는 마이크로스레드(microthread)다. 쓰레드가 아니기 때문에, 훨씬 가볍고 빠르게 고루틴을 실행할 수 있다. 10k이상의 고루틴도 쉽게 실행 할 수 있다. Non-blocking IO를 준비할 필요 없이 단일 코어에서 동시성을 구현 할 수 있다. 네트워크 프로그램의 경우 하나의 고루틴이 하나의 클라이언트 연결을 처리하도록 프로그래밍 할 수 있다. 고루틴, 채널, select의 역할은 아래와 같다.
  • 동시 실행 : 고루틴
  • 동기화와 고루틴 간 메시지 교환 : 채널
  • 다수의 고루틴 제어 : select

채널

채널은 고루틴을 동기화 하기 위해서 사용하는 메시지 큐다. 중요한 것은 first-class citizens으로 언어에서 직접 지원하는 기능이라는 점이다. 이 채널을 이용해서 메시지를 주고 받는 것으로 고루틴을 동기화 할 수 있다. Buffered 채널을 이용하면, 논 블록킹(non blocking)으로 메시지를 주고 받을 수 있다.

chan 데이터타입으로 "데이터타입"을 지원하는 채널을 만들 수 있다.
i := make(chan int)    // 양방향 채널
r := make(<-chan bool)           //읽기 전용 채널
w := make(chan<- []os.FileInfo)  // 쓰기전용 채널

패키지 시스템

Go는 패키지 시스템을 가지고 있다. 각 패키지는 "compress/bzip2", "golang.org/x/net/html"과 같은 구조를 가지며, "bzip2", "html"과 같은 패키지 이름을 이용해서 다른 패키지와 구분해서 사용해야 한다. 또한 대문자로 시작하는 함수만 사용할 수 있는데, io.Reader은 외부에서 사용할 수 있지만 bzip2.reader은 외부패키지에서 사용할 수 없다. 객체지향의 public, private 키워드를 대신할 수 있다.

고루틴, 채널, select

Go는 상태를 공유 함으함으로서 동시성 프로그램을 개발하기 위한 기능을 제공한다. 멀티스레드와 CPU 병렬처리 뿐만 아니라, 이벤트 기반 서비스를 위한 비동기 정보처리 기능도 포함하고 있다.
  • go, go func()를 이용해서 새로운 light-weight process(고루틴이라고 부른다.)를 만들 수 있다.
  • Channel타입을 이용해서 고루틴간에 buffered 메시지 채널을 만들 수 있다.
    • 전송 채널 : ch <-x. x데이터를 ch채널로 보낸다.
    • 수신 채널 : <- ch. ch 채널로 부터 데이터를 읽는다.

interface system

Go는 interfaces로 virtual inheritance를 대신한다. 간단하게는 메서드의 모음이라고 볼 수 있는데, 메서드 이름과 리턴값의 형식만 같다면 어떤 종류의 메서드에도 호출 할 수 있다. 예를 들어 io.Reader은 byte 슬라이스, error를 반환하는 Read 메서드가 필요한데, 메서드 이름과 반환 값 형식만 같다면 HTTP, 파일, in-memory buffer 등 어떤 종류의 메서드도 사용할 수 있다.

아래 예제를 살펴보자.
package main
 
import (
    "fmt"
    "io"
    "crypto/sha256"
)
 
type RepeatByte byte
 
func (r RepeatByte) Read(p []byte) (n int, err error) {
    for i := range p {
        p[i] = byte(r)
    }
    return len(p), nil
}
 
func main() {
    testStream := RepeatByte('a')
    hasher := sha256.New()
    io.CopyN(hasher, testStream, 1000000)
    fmt.Printf("%x", hasher.Sum(nil))
}

type RepeatByte가 struct가 아닌 byte라는데 주목하자. Go는 strcut뿐만 아니라 다른 모든 종류의 type에도 메서드를 만들 수 있다.

Empty 인터페이스(interface{})는 메서드를 가지지 않는 인터페이스로, 어떤 종류의 타입이라도 인터페이스 할 수 있다. 메서드를 호출 할 수 없지만, 다양한 타입의 데이터를 읽어서 변환 후 처리할 수 있다. C의 void *형과 비슷하다.

Go는 interface inheritance를 지원하지 않지만, 다른 인터페이스를 embed 하는 것으로 비슷하게 구현할 수 있다.

없는 것들

Go는 제너릭 프로그래밍, assertions, 포인터 연산, 상속과 같은 현대적인 언어들이 가진 기능들이 빠져있다.

예외의 경우, 보통의 언어들은 panic/recover 매커니즘을 이용해서 예외를 처리한다. Go는 Defer, Panic, Recover와 같은 내장 함수를 이용해서 직접 예외를 처리한다.

Go는 제너릭 프로그래밍과 상속을 직접적으로 지원하지 않는다. 대신 인터페이스를 이용해서 구현할 수는 있다. 인터페이스를 이용한 상속은 일장 일단있는 (쓸만한)구현이 가능하다. 반면 제너릭 프로그램의 경우 empty 인터페이스를 이용한 구현은 부자연스럽다는 느낌이 든다.

go가 제공하는 도구들

  • go build : 소스코드 파일로 부터 GO 실행파일을 만든다.
  • go test : 마이크로 벤치마크와 유닛 테스트.
  • go fmt : 소스코드의 포맷을 체크한다.
  • go get : 원격 패키지를 설치한다.
  • godoc : Go 문서 서비스
  • gorename : 변수, 함수 이름을 바꿔준다.

참고

Go 언어에 대한 기본적인 설명을 하지는 않을 것이다. Go 웹 사이트에 가면, 몇 개의 tutorial 문서들을 찾을 수 있다(게다가 번역 문서도 있다.).