2022-08-23 Go (Golang) Language Tasting
Table of Contents
Goal
약간의 짬이 난 김에 Go 언어에 대해 알아보고 간단히 실습해본다. 룩시드랩스 상황에 적용가능성과 활용할 수 있는 부분에 대해 생각해 본다.
What is Go or Golang?
Intro
// Hello World by Go!
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
최근 몇년사이에 개발자들이 배우고 싶어하는 언어로 Top 3 에 오르고 있다. 어떤 언어이고 특징은 어떠할까.
Go 는 2009년에 구글의 로버트 그리즈머, 롭 파이크, 켄 톰프슨과 같은 레전드 프로그래머들이 개발한 프로그래밍 언어이다.
Go 언어가 개발되기 전 구글의 infra software 는 대부분 C++ 로 작성하고 있었다고 한다. 분산 빌드 시스템이 잘 갖추어져 있었지만 서버 빌드 하는데 45분 이나 걸렸고, 반복되는 긴 컴파일 시간에 지친 위의 구글 엔지니어 세 명이 웹 서버와 같은 대규모 프로그램을 개발하는데 적합한 언어를 만들어 보자! 라고 하면서 Go 개발이 시작되었다고 한다.
- C++ 은 빠르지만 복잡하다.
- 사용자가 배우기 어렵다.
- 컴파일 시간이 오래 걸린다. C++ 은 현재도 광범위하게 사용되고, 빠른 좋은 언어지만 위와 같은 단점이 있다. 특히 메모리 관련한 부분들을 다룰 때는 디버깅도 어렵고 난감할 때가 많을 것이다. 그래서 위의 구글 엔지니어들이 새로운 언어를 만들기 시작했다.
특징
일반적 특징
- GC (Garbage Collection) 를 지원하며, concurrent 구조를 잘 지원하는 것으로 알려져 있는 컴파일 언어이다. 메모리를 매뉴얼로
건드릴 수접근할 수 있으면서 Garbage Collection 이 된다는 것은, 메모리 관련한 디버깅이 상당히 골치아프다는 점을 생각해보면 개발자에게 심히 안심이 되는 부분인 것 같다. - 그다지 좋지 않은 HW 환경에서도 빠르게 컴파일 되도록 설계 되었다고 함. 태생의 이유를 보면 맞는 말일 듯.
- C++ or Java 에 있는 특징 중 타입 상속, Generic, assertion, overloading, 포인터 연산은 현 Go 에 포함되어 있지 않다. 급하진 않지만 어느 시점에는 도입이 될 예정이라고 함.
- 정적 타입 언어이다!
지원 플랫폼
- Linux / macOS / BSD / Windows / Some mobile platforms
Syntax
- 대체로 C 와 비슷하다.
- 코드 블록은 중괄호로 둘러싼다.
- 제어문은 for, switch, if 를 포함한 일반적인 제어구조를 가지고 있다. 하지만 while, do-while 은 없다.
- 실제로 Go 를 써보니 js, python 과 비슷한 느낌을 주었다. 문법 자체는 배우기 어렵지 않은 듯!
- 간단한 web-app
web-app Go Code
// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build ignore package main import ( "fmt" "html/template" "log" "net/http" "os" "regexp" ) type Page struct { Title string Body []byte } var templates = template.Must(template.ParseFiles("edit.html", "view.html")) var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") func (p *Page) save() error { filename := p.Title + ".txt" return os.WriteFile(filename, p.Body, 0600) } func loadPage(title string) (*Page, error) { filename := title + ".txt" body, err := os.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func mainHandler(w http.ResponseWriter, r *http.Request) { p, _ := loadPage("index") fmt.Fprintf(w, "%s
Concurrency (동시성)
Go 위키에도 따로 동시성 특징이 있는 것을 설명해 놓은 것을 보면 확실히 장점이 있게 설계된 것으로 보인다. 동시성 혹은 병렬성 이라고 하면 멀티 스레딩 / 멀티 프로세싱 정도만 생각하기 쉬운데, 이 2 가지 기능 뿐 아니라 AsyncIO, event-based 서버, DB or Network 작업과 같이 시간이 오래 걸리는 연산을 하는 동안 프로그램이 다른 일을 하는 것을 말한다. 이를 위한 언어 설계가 잘 되어 있다고 하는데 내가 직접 동시성 코드를 작성해 보지 않아서 아직 잘 모르겠음.
Concurrency (동시성) & Parallelism (병렬성)
위 두 개념을 혼재해서 쓰는 경우가 많지만 둘은 엄연히 다른 개념이다.
- 동시성 - 여러 일을 분할 해서 동시에 처리. 시분할 처리 개념과 가깝다.
- 병렬성 - 정말로 ‘동시’ 에 여러 가지가 처리되는 것.
‘문법적’ 으로 얼마나 간단한지는 아래 코드를 보자. go 키워드 하나로 goroutine 이 실행된다. 동작은 https://go.dev/play/p/U9ZZuSql8- 서 웹으로 run 해보자.
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(3 * time.Second)
fmt.Println("main function")
}
다른 언어에 비해 왜 멀티 프로그래밍에 왜 장점이 있을까?
“결론은 프로세스와 쓰레드를 사용하지 않고 goroutine 이라는 고유의 light-weighted 방식을 사용하기 때문이다.”
그렇다면 프로세스, 쓰레드를 사용하지 않는 것이 왜 이득인가? 둘 은 필연적으로 OS 가 관여하기 때문에 오버헤드가 클 수 밖에 없다. Context switching 이 일어날 수 밖에 없고 이 때 OS 가 관여하기 때문에 태생적으로 오버헤드가 발생한다. 물론 쓰레드가 프로세스 보다는 context switching 이 덜 일어나지만 발생한다는 사실은 분명하다.
그래서 Go 에서는 goroutine 이라는 것을 사용하는데,
- OS 에 요청하지 않고 언어 내부에서 자체적으로 관리하고 실행한다.
- 내부에서 시분할 처리를 하니 concurrency (동시성) 은 만족하지만 병렬성을 만족하는지는 알 수 없다. 하지만 만족하게 의도할 수는 있다고 하는데 이 부분은 파이썬의 coroutine 과 비슷한 점인 듯 하다.
근데.. 파이썬의 coroutine 과 Go 의 goroutine 은 어떻게 다른거지? 궁금해서 찾아보니 스택오버플로우에 좋은 답변이 있었다. (아래 quote 참고)
- coroutine (Python)
- 프로그래머가 coroutine 이 언제 시작하고, 중단되고, 제어권을 넘길지 명시적으로 정한다.
- goroutine (Go)
- Go 내부에서 알아서 제어를 다한다.
즉 동시성 프로그래밍을 할 때 상당히 골치아픈 부분인 서로다른 프로세스, 스레드, 루틴 간의 제어를 비교적 알아서 (?) 해준다는 것 같다.
실제로 파이썬으로 비동기 프로그래밍을 했을 때 가장 머리아픈 부분이 서로 다른 코루틴간의 충돌 및 보틀넥 이었다. 이를 명시적으로 확인하고 어느정도 제어해 주어야 했다. 동시성 혹은 병렬 프로그래밍에서 이 부분이 제대로 되지 않으면 차라리 병렬 프로그래밍을 하지 않는게 소프트웨어의 안정성, 개발의 편의성 등 여러 부분에서 낫다고도 생각될 정도로 병렬 프로그래밍은 잘 설계되어야 하는 부분이다.
이런 점의 걱정을 많이 덜어줄 수 있다면 goroutine 개념은 상당히 혁신적인 개념일 것 같다.
A quote from Stackoverflow.com IMO, a coroutine implies supporting of explicit means for transferring control to another coroutine. That is, the programmer programs a coroutine in a way when they decide when a coroutine should suspend execution and pass its control to another coroutine (either by calling it or by returning/exiting (usually called yielding)).
Go’s “goroutines” are another thing: they implicitly surrender control at certain indeterminate points1 which happen when the goroutine is about to sleep on some (external) resource like I/O completion, channel send etc. This approach combined with sharing state via channels enables the programmer to write the program logic as a set of sequential light-weight processes which removes the spaghetti code problem common to both coroutine- and event-based approaches.
쉬운 Build
아래처럼 간단히 built-in build 명령어로 바로 바이너리 파일을 만들 수 있다. 그리고 지원하는 모든 플랫폼에서 실행이 가능하다.
$ go build hello.go // hello 바이너리 파일이 생성됨
$ ./hello // 바이너리 파일 실행
파이썬에서 플랫폼 별로 패키징할 때 고생했던 걸 생각하면 신세계다…
Error Handling
Go 언어는 에러 처리에 있어서 기존의 try-catch-finally 형식은 코드를 복잡하게 만들고, 읽기 어렵게 만든다고 판단하였다. 기존 C, C++ 에서는 반환되는 값 자체를 가지고 (0, 1, -1 등) 에러 여부를 판단했기 때문에 에러의 상세한 판단이 어려웠다. 따라서 원한다면 에러 판단이 용이한 코드를 따로 만들어야 하고 이는 코드의 복잡성을 증가 시킨다.
Go 언어 에서 함수는 복수개의 값을 반환할 수 있는데 이를 통해 에러가 발생했을 때 리턴값과 함께 에러 메세지도 동시에 반환하여 에러상황을 자세히 알 수 있게 하였다. 아래 Go 의 built-in os.ReadFile() 함수를 살펴보자.
func os.ReadFile(name string) ([]byte, error)
함수 호출 후에 []byte, error 를 반환하는 것을 볼 수 있다. 이것을 어떻게 쓸 수 있냐면…
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}
이런 방식으로 따로 에러를 받아서 처리할 수가 있다.
제약 사항
- Generic 과 같은 일부 기능은 의도적으로 빠졌다.
- 포인터 연산이 안되는 등 C 와 다른 부분은 분명히 존재하기 때문에 kernel, device driver, embedded software 같은 low-level 의 프로그램엔 적합하지 않다고 함. 주소를 참조할 수는 있지만, 연산은 안된다.
- 하드웨어 가속을 사용할 수 없다. (그래픽스 분야에 맞지 않는다는 뜻)
- 따라서 유틸리티 or 네트워크 서비스와 같은 프로그램을 만들 때 유용하다.
LooxidLabs 에서?
그래서 룩시드랩스에서 Go 는 얼마나 활용가치가 있을까? 룩시드랩스에서 하는 일을 생각 해보면,
- Data Analysis (활용 가치 상)
-
[Data Science and the Go Programming Language: School of Professional Studies Northwestern University](https://sps.northwestern.edu/stories/news-stories/data-science-go-programming-language.php) - 위 내용을 읽어보면 Data Science 에서 Go 신봉자가 된다.
- 객관적으로 보면 파이썬 처럼 GIL (Global Interpreter Lock) 을 통한 single-thread 방식이 아니므로 Go 가 C / C++ 처럼 빠른 연산능력을 가질 것으로 보는데 자료를 좀 찾아보니 그러한 듯 하다. 그렇다면 데이터 분석에 있어서 매우 강점이 있을 것이다.
-
- Backend (활용 가치 )
- 일단 실제 구글 서버의 일부로 사용되고 있다.
- 웹이든 모바일이든 반응속도는 사용자에게 가장 중요한 요소 중 하나일 것이다. 속도 측면에서 Go 는 항상 탑랭크를 달리고 있다. 하지만 속도만으로 판단할 수 없고, 지원 API, 안정성도 살펴봐야 하는데 지식이 없다.
- 조금 찾아보니 이더리움 공식 Go 클라이언트인 goeth 가 존재한다. 안정성과 속도가 중요한 블록체인 분야에서, 그리고 이더리움과 같은 메이저 플랫폼에서 공식적으로 지원한다는 건 의미가 있다.
- Node.js 가 좋냐 Go 가 좋냐 갑론을박도 있는데 속도가 빠르다 보니 데이터를 많이 처리해야 하는 서버를 구축해야 하면 Go 를 사용하여 이득을 볼 수 있을 듯.
- Frontend (활용 가치 ?)
난 잘 모르겠다.- Go GUI 프레임웍이 꽤 존재하긴 함.
- On Mobiles (활용 가치 ?)
난 잘 모르겠다.- 신기하게도 Android (Go Edition) 이 있다.
Android (Go edition) | Android - 가벼운데 성능이 좋아서 저가의 안드로이드 폰에 들어가는 듯?
- 안드로이드 진영에서 서포트 하고 실제로 OS 까지 출시된 걸 보면 꽤나 low-level 영역에서도 활용도가 좋은 것으로 생각된다.
Conclusion
-
C / C++ 은 성능이 좋은 반면에 메모리 관리가 어렵고 배우기 어려운 단점이 있다. 또한 오래된 언어여서 coroutine, goroutine 과 같은 개념을 사용하지 않고 동시성 프로그래밍이 중요한 현시대에 뒤떨어진 면이 있다.
-
이에 반해 Go 언어는 최신 언어답게 성능도 좋으면서 Python 과 같이 배우기 쉽다는 장점도 같이 있다. 또한 backend, frontend, mobile, data analysis 등 넓은영역에서 실제로 사용되고 있다.
-
이는 분명 큰 장점들이며, 2009 년에 처음 세상에 나왔고 아직 버전이 1.19 정도 밖에 안되는 만큼 커뮤니티가 더 성숙하고 더 많은 라이브러리가 축적 된다면 멋진 언어가 될 것 같다.
Reference
- 물어보는 사람이 많아서 정리했습니다 Go언어
- https://betterprogramming.pub/build-a-simple-todolist-app-in-golang-82297ec25c7d
- https://medium.com/curg/고루틴-go-언어의-동시성-모델-1045986cc001
- Android (Go edition) Android
- https://madappgang.com/blog/backend-development-with-golang/
- Golang: What and Why?