초심자를 위한 고루틴 활용가이드 1장 고루틴이 뭐여?

ironpark·2021년 4월 8일
2

어디 잘해보자 GO

목록 보기
1/1

🚨 독자
파이썬,JS 등의 동적언어 계열은 다루어보았으나 기존에 시스템 언어, 컴파일 언어 등은 다루어보지 않았거나 최소한으로 다루어본 사람 혹은 병렬 프로그래밍에 익숙하지 않은 사람을 대상으로합니다.

🚨 주의사항

  • 이글은 최소한 Golang 의 syntex를 숙지하고 함수와 변수 그리고 구조체와 메소드를 다룰수 있는 수준을 요구합니다. 이에 해당하지 않는다면 https://tour.golang.org/ 에서 기초적인 내용을 보는걸 추천합니다.
  • 프로세스/스레드와 고루틴간의 차이등은 설명은 의도적으로 다루지 않으며 생략된 요소가 많습니다.

연재 순서

  • 📘 고루틴이 뭐여?
  • 동시성과 병렬성 그리고 채널과 뮤텍스
  • 채널 쓰기 좋은날
  • 뮤텍스 쓰기 좋은날
  • 뮤텍스 잘좀 써보자
    • 부록 atomic 연산자
  • 작업 멈춰! 아니 기다려!
  • 수영좀 해볼까?

연재순서, 제목 및 내용은 변경될 수 있습니다.

시작하며

Go 언어를 관심있게 학습하는 사람이라면 당연히도 고루틴(Goroutine)채널(channel)에 대해 많이 듣거나, 사용해보았을 것이다. 다만 이글을 보는 당신이 이런 개념에 익숙하지 않은 프로그래머라면 어떻게 사용해야 실수를 덜하고 어떤 경우에 사용해야하는지, 실제로 내가 코드를 작성할때 어떤식으로 활용해야 할지 감도 잡히지 않을 것 이라고 생각한다.

이상태에서 fan-out/in 등의 pipeline 패턴 등을 배워 뭐에 쓴다는 말인가. 직장 상사가 볼때만 간헐적으로 터지는 무서운 버그가 생길지도 모르는 일이다. 나 또한 많은 버그를 생산한 이력이 있다. 이글은 그동안 내가 거친 시행착오를 포함한 회고록이라고 보아도 좋을 것이다. 각설하고 본론으로 들어가보자.

고루틴

우리는 모두 모르는 사이 고루틴을 사용하고 있었다.

package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

위 예제는 우리가 새로운 프로그래밍 언어를 배울때 해당언어에 대하여 별로 아는게 없는 최소한의 지식상태에서도 작성하고, 이해할 수 있기때문에 다양한 매체에서 지겹도록 보는, 혹은 어떤 밈으로 소비되곤 하는 그 유명한 안녕세계! 예제이다.

이 최소한의 예제에서도 고루틴을 우리는 사용하고 있다는 것 을 알고 있는가? go 어플리케이션을 작성할때 main 함수는 프로그램이 실행될 진입점이자, 1개의 고루틴을 통해 실행되는 함수 이다. 이말은 즉 우리가 어떠한 프로그램을 작성하던 1개의 고루틴은 무조건적으로 실행된다라고 볼 수 있다.

그리고 main 고루틴 이하 메인루틴 이 종료될때, 프로그램은 종료된다.

그래서 고루틴이 뭐라고?

Goroutine 의 철자를 보자 아무리 세안을하고 눈을비비고 곰곰히 생각해보아도. 이는 Go + Routine 의 합성어이다. 루틴이 무엇인가. 우리가 운동을 할때도 운동루틴, 생활루틴 등등 우리가 사용하는 말에 답이 있다. 우리는 자주하는, 어떤 순서를 가지는 일련의 행동을 "--" 루틴 이라고 부르곤 한다.

고루틴은 일련의 작업을 실행하는 일꾼 이라고 볼 수 있으며, 특정함수를 고루틴으로 실행한다는 뜻은 새 일꾼에게 이 루틴(함수) 를 실행하라고 명령을 내린다는 뜻이다.

사실 일꾼은 정확한 표현은 아닙니다, 병렬성만을 이야기 한다면 맞는말이라고 볼수 있지만요. 표현이 수정되거나 후속 아티클에서 부연설명이 들어갈 예정입니다.

고루틴 이 가진 성질

한번 아주 간단한 코드를 보고 출력을 예상해 봅시다. Go Playground

package main

import "fmt"

func main() {
	fmt.Println("hello velog! hello go!",1)
	fmt.Println("hello velog! hello go!",2)
	fmt.Println("hello velog! hello go!",3)
}

최소한의 문법을 익혔다면 당연히 출력을 예상할 수 있을 것 이다. 물론 반전은 없다.

hello go! 1
hello go! 2
hello go! 3

Program exited.

golang 에서는 고루틴을 사용할때, go 키워드를 실행하려는 함수 앞에 붙인다. 그렇다면 이건 어떤가?

package main

import "fmt"

func main() {
	go fmt.Println("hello velog! hello go!",1)
	go fmt.Println("hello velog! hello go!",2)
	go fmt.Println("hello velog! hello go!",3)
}
Program exited.

아무것도 출력되지 않고 프로그램이 종료되었다, 메인루틴이 내 명령을 거절한 것 일까? 아니면 컴파일러가 내코드를 무시했나? 아니 그럴리 없다 차근 차근 살펴보자.

  1. 아무것도 출력되지 않고 프로그램이 종료되었다.
  2. 치명적인 오류(panic)나 에러가 있었나? 없었다.
  3. 이는 메인루틴이 본인에게 주어진 모든 일을 해결하고 종료되었다는것을 의미한다.
  4. 이전코드와의 차이점은 go 키워드를 fmt.Println 함수앞에 붙여주었다는 것 하나다.

여기까지 읽었는데 이유를 모르겠다면. "그래서 고루틴이 뭐라고?" 를 다시 읽어보자.

고루틴은 일련의 작업을 실행하는 일꾼 이라고 볼 수 있으며, 특정함수를 고루틴으로 실행한다는 뜻은 새 일꾼에게 이 루틴(함수) 를 실행하라고 명령을 내린다는 뜻이다.

자세히 읽어보면 새 일꾼 이라고 했다. 즉 main 함수를 실행하고 있는 고루틴이 아니라 새로운 일꾼에게 fmt.Println() 이라는 일을 할당한 것이다. 그러면 메인루틴은 할일이 없게 되고 이로인해 메인루틴이 종료되면 결과적으로 프로그램이 바로 종료되는 것은 당연한 일이다.

package main

import "fmt"
import "time"

func main() {
	go fmt.Println("hello velog! hello go!",1)
	go fmt.Println("hello velog! hello go!",2)
	go fmt.Println("hello velog! hello go!",3)
	time.Sleep(time.Second)
}
hello velog! hello go! 3
hello velog! hello go! 1
hello velog! hello go! 2

이번에는 main 함수가 종료되기 이전에 time.Sleep(time.Second) 을통해 1초동안 기다리는 코드를 추가했다. 이로인해 고루틴으로 실행된 함수들이 프로그램,메인루틴이 종료되기 이전에 실행될 수 있는 시간이 생겼기 때문에 출력이 생성되었다. 그런데 그 순서가 조금 이상하지 않은가?

코드는 분명 1,2,3 순으로 작성하였는데 정작 출력은 3,1,2 순으로 되었다.

사실 이는 지극히 당연한 것이다 (정확한 비유는 아니지만) 생각해보자 일꾼 3명에게 순서대로 일을 시키면 3명이 일을 처리하는 시점은 아무리 3명의 일꾼이 매우 비슷한 지능,체력을 가지고 있다고 하더라도 누가 1초의 오차도 없이 일을 마무리하지 못할 것이다.

결국 여러 고루틴을 이용하여 같은 일을 시켰을때, 어떤 고루틴이 먼저 일을 끝낼지는 알 수 없다는 이야기다.

그걸 어디다 쓰니

여러 일꾼에게 일을 줄 수 있다는 것은, 여러가지 일을 동시에 진행할 수 있다는 것이다. 누구는 문서작성시키고 누구는 청소시키고 다양한 일을 동시에 처리함으로서 프로그램의 효율 및 성능을 극대화 할 수 있습니다.

이글은 초고 입니다. 앞으로 오타, 띄어쓰기 표현이나 비유를 다듬을 예정입니다.
탈고의 과정을 거치지 않고 포스팅을 하는 이유는 저의 귀찮음과 빠른 피드백을 통한 수정을 위함입니다.

profile
망망대해를 떠다니는 개발자

0개의 댓글