💡 Go 언어에 대해서 책을 읽고 정리한 것이다. 등장 배경, 문법, 특장점, 동시성, 예제 기반 등을 담고 있다.
그림 출처 - 하룻밤에 읽는 Go 언어 이야기 (신제용 지음)
// C
int a;
int b[10];
// Go
var a int
var b [10]int
Go 언어는 뼛속까지 동시성 지원을 위한 언어
병렬성,
문제를 여러 연산으로 나누고 그것을 여러 프로세서나 코어 혹은 분산환경에서 동시에 실행하는 형태를 뜻한다. 연산이 일어나는 레벨을 기준으로 한다면 비트, 명령어, 데이터, 마지막으로 태스크 레벨로 분류할 수 있다.
동시성,
여러 연산이 동시에 수행되고 이 연산들이 상호작용이 발생할 수 있는 시스템의 특성을 말합니다. 연산들은 단일 프로세서나 코어에서 시분할 방법으로 동시에 실행될 수도 있고 여러 프로세서나 코어 또는 분산환경에서 동시에 실행될 수도 있습니다.
즉, 동시성은 CPU를 지정하여 연산을 동시에 수행하는 것이 아니라, 동시에 일어나는 일들에 대한 정의와 어떤 리소스를 공유할지에 대한 내용을 기술하는 방식이다.
(스레드 → 동시성 프로그래밍)
프로세스가 여러 개라면 어떤 일이 생길까?
Go 언어에는 동시성을 지원하기 위해 goroutine이 있다. 비동기적으로 함수를 실행하기 위해서 사용하는데, 다른 언어에서는 어떻게 goroutine과 유사한 일을 실행하는지 알아보자.
// Java
class SimpleThread extends Thread {
public void doSomething() {
// ...
}
public void run() {
doSomething();
}
}
public class ThreadTest {
public static void main(String[] args) {
new SimpleThread().start();
}
}
- > Thread를 상속하여 run 메서드를 오버라이드 한다.실제로 호출하는 쪽에서는 해당 스레드 인스턴스를 생성하여 start 메서드를 호출하면 새로 생성한 스레드로 함수 실행할 수 있다.
// C
void * doSomething() {
// ...
}
int main() {
pthread_t thread_t;
if (pthread_create( & thread_t, NULL, doSomething, NULL) < 0) {
perror( << thread create error >> );
exit(0);
}
return 1;
}
- > pthread를 선언하고 pthread_create를 이용해 실행하는 방법이다.함수의 인자로 실행할 함수 포인터를 전달한다.
// Go
func doSomething() {
// ...
}
func main() {
do doSomething()
}
- > 간단하다.
goroutine은 위 둘과 유사하지만 다른 속성을 가진다. 동일한 주소 공간에서 다른 goroutine들과 함께 동시에 실행되고, 스레드보다 가벼우며 스택 주소 공간의 할당이 적다.
C 프로그램이 실행될 때 주소 공간은 (Stack → Free Memory ← Heap, Static Data, Code) 로 이루어져 있다. C에서 스택은 연속적인 메모리 블록으로 구성되어 있어 새로운 스레드가 생성되면 스레드가 최대한 사용할 만큼의 메모리 블록을 잡게 되는데, 일반적으로 1MB의 스택 영역을 할당받는다. 실제로 몇 KB만 있으면 충분한 태스크도 있지만, 1MB를 받게 된다.
Go 언어의 경우 스택을 링크드 리스트로 관리한다. 이는 할당받은 공간이 충반하지 않으면 추가로 스택을 늘려 달라고 요청해 늘리는 방식이다. 또한 goroutine은 매번 커널 스레드를 생성하여 수행하지 않고, 일부 커널 스레드로 멀티플렉싱되어 사용되므로 효율적이다.
멀티플렉싱? (인터럽트로 인한 잠시 다른 공부)
func main() {
c: = make(chan int)
go func() {
// ...
c < -1 // 1을 채널 c로 보낸다.
}()
< -c // 채널 c에서 값을 받아 사용하지 않고 버린다.
fmt.Printf("main end")
}
Go 언어의 핵심 포인트 중 하나인 channel에 대한 좋은 글을 첨부한다.
또 중요한 메모리 모델이라는 개념,
책에서 검색 시스템을 예시로 동시성을 설명한다.
그림 출처 - 하룻밤에 읽는 Go 언어 이야기 (신제용 지음)
var {
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
}
type Search func(query string) Result
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
// Google 함수의 순차적 검색 방식
func Google(query string)(results[] Result) {
results = append(results, Web(query))
results = append(results, Image(query))
results = append(results, Video(query))
return
}
// goroutine을 이용한 검색 방식
func Google(query string)(results[] Result) {
c: = make(chan Result)
go func() {
c < -Web(query)
}()
go func() {
c < -Image(query)
}()
go func() {
c < -Video(query)
}()
for i: = 0;i < 3;i++{
result: = < -c
results = append(results, result)
}
return
}
func main() {
results: = Google("동시성")
fmt.Println(results)
}
추가로 아래와 같이 channel 타입에만 사용되는 switch 구문인 select와 timeout을 더한 예시도 참고하면 좋다.
c := make(chan Result)
go func() { c <- Web(query) } ()
go func() { c <- Image(query) } ()
go func() { c <- Video(query) } ()
timeout := time.After(80 * time.Millisecond)
for i:=0; i<3; i++ {
select {
case result := <-c:
results = append(results, result)
case <-timeout:
fmt.Println("time out")
return
}
}
return
🤦🏻♂️하룻밤은 무슨... (아직 멀었구나)