vidigummy Go!

Go와 Go routine

vidi 2023. 1. 3. 17:04

Go에는 Go routine이라는 것이 있더랬다.

Golang 특유의 방식인데, Go가 동시성 프로그래밍이라는 것을 하게 되는 이유라 할 수 있다.

 

여기서 병렬성 프로그래밍과 동시성 프로그래밍에 대한 것을 이해하고 넘어가야하겠다.

 

동시성이란, 여러개의 일을 동시에 처리되는 것 처럼 보이게 Context Switching을 해 가면서 일을 처리하는 것을 의미한다. 카페에서 여러개의 주문이 들어왔다고 했을 때, 커피 샷을 내리는 동안 빵을 30초 동안 데우는 것과 같이 일을 처리하는 것이다. 일을 처리하는 사람은 단 한명(코어 하나)이지만 샷이 내려지는 시간 혹은 빵이 돌아가는 그 시간 동안 다른 일을 할 수 있어 순차적인 접근 보다는 훨씬 빠르게 일을 처리할 수 있다.

 

병렬성이란, 여러개의 일을 여러개의 일꾼(코어)이 할 일을 나눠 각자 처리하는 방식이다. 앞서 설명했던 상황에서 병렬성 프로그램은 여러개의 주방을 가진채, 일을 나눠 처리한다. 한명이 끙끙대며 할 일을 여러개로 나눠 처리하니 확실히 빠르게 처리할 수 있을 것이다.

말만 들어본다면, 병렬성이 정말 좋게 느껴질 것이다. 실제로 그렇게 생각하는 사람도 많고. 하지만, 문제는 여기서 발생한다. 내가 돈이 없어서 카페에 하나의 주방만을 놔야 한다면? 그 주방에서는 순차적으로 일을 할 수 밖에 없다면? Java와 Spring을 예로 들어보자. Java는 Multi Thread 방식을 통해 병렬적으로 Task를 수행하는 대표적인 언어이다. 멀티 쓰레드를 쓸 수 있을 때는 좋지만… 내가 싼 서버를 쓰고 있다면 병렬처리를 하기가 무척이나 힘들 것이다. 하지만 요새는 어떤 시대인가? K8S와 MSA등의 방법론 등을 통해 Scale Up이 아닌 Scale Out을 하는 시대이다! 작은 서버에게 병렬이란… 좋은 것은 알지만, 그림속의 떡이 아닐까…? 는 내 생각이다. 저번 프로젝트에서 Java를 이용한 Azure Function으로 병렬 처리를 했었는데, 하루에 유지 비용만 4000원이 들었더랬다…. 이건 아니지 않을까…?

 

뭐 내가 틀렸을 수도 있는데, 일단 내가 이번에 쓴 Go routine에 대해 설명을 대충 해줄 수 있도록 하겠다. 사용자의 github 계정을 분석해 특정 서비스를 제공해야 하는데, 남의 DB를 호출하는 일이다 보니 API 호출이 오래 걸리는 문제가 있었다. 사용자 레포의 세부 정보를 모두 봐야 하는데, 사용자가 레포 정리를 안 하는 죄금 무서운 사람이라면… 처리 시간은 기하급수적으로 늘어날 것이다.

 

이를 해결하기 위해! Go routine을 사용했다! 그렇다! 좋더라고!

사용하는 방법은 간단하다. 잠시만…

func GetUserInfo(userName string) (map[string]int, error) {
	// 내 레포에 어떤 언어가 얼마나 있는지 확인할 수 있음.
	repoList, err := getRepo(userName)
	if err != nil {
		fmt.Println(err)
	}
	// 레포 리스트 확인용(무슨 레포가 있는지)
	// fmt.Println(repoList)

	// 랭귀지맵은 사용자의 언어 map이라고 할 수 있죠
	languageMap := make(map[string]int)

	// api들 동시에 호출, 빠르게 가져오기
	// 403 뜨네? 큰일난걸지두...
	repoChan := make(chan map[string]int)
	for _, repoName := range repoList {
		go getRepoLanguages(userName, repoName, repoChan)
	}
	// 가져온 리스트 싹 다 한 레포에 집어넣기
	for i := 0; i < len(repoList); i++ {
		tmpLanguageMap := <-repoChan
		for key, value := range tmpLanguageMap {
			checkData, exists := languageMap[key]
			if !exists {
				languageMap[key] = value
			} else {
				languageMap[key] = checkData + value
			}
		}
	}
	fmt.Println(languageMap)
	return languageMap, nil
}
func getRepoLanguages(userName string, repoName string, c chan<- map[string]int) {
	client := github.NewClient(nil)
	ctx := context.Background()
	languages, _, err := client.Repositories.ListLanguages(ctx, userName, repoName)
	if err != nil {
		fmt.Println(err)
	}
	c <- languages
}

 

뭐 대충 이렇게 Channel(Stack이라고 생각하면 편하다)을 만들어 두고, 동시성으로 처리할 함수를 슥 돌려준다. 그러면, 먼저 끝난 순서대로 돌려주는데, 그 속도가 확실히 순차처리보다는 빠른 것을 알 수 있었다.(아마도 API 호출 및 대기하는 시간이 드릅게 오래 걸리는 것이 원인일 것이라고 생각한다.)

 

사실 이걸로 Go 언어 공부를 마쳤다(? 뭐하는 친구일까). 사실 저 코드를 만들고 아까 말했던 기능을 만든 것은 모두 웹서버를 만들기 위해서이다. 기초 문법에 대한 설명을 하지 않은 것은 미안! 하지만 귀찮다! 인터넷에 짱 많다! 다음은 Gin에 대한 설명으로 돌아올게!

 

미안! 글을... 쓰기 귀찮더라고!

 

'vidigummy Go!' 카테고리의 다른 글

vidigummy go!  (0) 2022.12.19