이 과목은 Go언어를 활용한 '프로젝트'를 만드는 것이 목적이 아니라 용법을 배우는 것이 목적인 과목이기 때문에 기본적인 예제 코드와 알고리즘 성격이 강한 실습 코드를 다뤘습니다. 그런데 사실 강의 후반부에 시작한 'defer와 panic()' 챕터부터의 학습 내용은 실제 프로젝트에 적용했을 때 그 편리함과 장점을 실감할 수 있습니다. 에러 처리도 굉장히 중요한 용법이지만 이전 강의에서 '고루틴'을 학습하고 Go언어의 장점인 비동기성 프로그래밍을 직접 느껴봤습니다. 고루틴을 사용하면 비동기적으로 여러개의 함수를 실행할 수 있고 이를 활용해 각 데이터를 동시에 서버에 전송할 수 있는 기능을 가지고 있는 것입니다.
그런데 고루틴을 사용할 때 우리가 이전 강의에서 생각해보지 못한 문제가 발생할 수 있습니다. 우리가 알고있는 함수의 기본 형식 예시를 살펴보고 어떤 문제가 발생할 수 있을지 생각해봅니다.
위 예시는 쉬운 기본 코드입니다. 익명함수 클로저에서 두 수를 더하는 연산을 하고 result
에 결괏값을 저장합니다. 그리고 printResult()
함수에 result
가 전해지고 출력됩니다. 동기적인 함수들의 실행 흐름은 전혀 문제될 것이 없습니다. 위에서부터 순서대로 함수가 호출되고 종료되기 때문에 자연스러운 흐름을 하기 때문입니다.
그런데 만약 익명 함수를 고루틴에서 호출하면 어떻게 될까요? 익명함수에서 연산한 결괏값이 printResult()
함수에 전달되기도 전에 프로그램이 종료될 것입니다. 이 예시는 두 함수만이 값을 주고받지만 만약, 프로그램이 커서 엄청나게 많은 고루틴이 생성되고 값을 주고받는다면 걷잡을 수 없이 문제가 커지게 됩니다. 고루틴은 비동기적으로 실행되기 때문에 다른 고루틴에서 실행되는 함수의 종료 여부와는 상관없이 진행됩니다. 그래서 만약 값을 연산하고 반환하는 고루틴이 먼저 종료된다면 문제가 발생하게 됩니다. 그래서 고루틴끼리 서로 값을 주고받는 통로가 필요합니다. 그것이 바로 이번 강의에서 배우는 채널(Channel)입니다. 이는 고루틴을 사용하는데 있어 굉장히 중요한 역할을 합니다.
아래 코드를 바로 실행해보세요.
지난 강의에서 모든 고루틴이 종료되기 전까지 대기하는 방법으로
- 주먹구구식으로
main()
함수 마지막에fmt.Scanln()
함수를 쓰는 방법 WaitGroup
사용으로 모든 고루틴 종료 대기
를 학습했습니다. 그런데 말 그대로 이는 고루틴이 모두 종료되는 것을 기다리게 하는 용법일 뿐이지 고루틴 사이에 흐름을 제어하지는 않습니다. 채널은 고루틴 사이에서 값을 주고받는 통로 역할을 하고, 송/수신자가 서로를 기다리는 속성때문에 고루틴의 흐름을 제어합니다. 그리고 채널의 데이터를 주고 받을때까지 해당 고루틴을 종료하지 않아 별도의 lock을 하지 않고도 데이터를 동기화 하는데 사용합니다.
채널은
- "make(chan 데이터타입)" 형식으로 생성합니다.
- 채널의 데이터 송/수신은 '<-' 연산자를 이용합니다.
- 체널에 값을 보낼 때는 채널 <- 데이터, 채널에서 값을 받을 때는 <- 채널 입니다. 값을 받을 때는 :=이나 =을 이용해 변수에 바로 값을 대입할 수 있습니다.
- 채널에서 값을 받을 때까지만 대기합니다. 가져오면 바로 다음 코드를 실행합니다.
위 그림을 보면 쉽게 이해할 수 있습니다. 아래 채널을 이용해 수정한 코드를 바로 실행해보세요.
채널의 값을 수신할 때 꼭 변수에 대입하지 않아도 됩니다. 단지 <- 채널 형식으로 입력하면 수신을 받을 때까지 대기하고 별도의 값을 받지는 않습니다. 여기서 주의할 점은 위에서 고루틴B의 함수 종료 시점입니다. 고루틴B는 고루틴A로 송신한 데이터가 수신이 될때까지 대기합니다. 그런데 그 이후에 main() 함수(고루틴A)가 종료되어 프로그램이 종료되면 고루틴B는 끝까지 실행되지 않고, 종료되지 않은 채 프로그램이 종료될 수 있습니다.(채널의 역할이 끝났다면 고루틴의 본질은 비동기이기때문에) 따라서 데이터 송/수신 시점이 함수가 대기하는 시점과 관련있기 때문에 주의해야합니다.
아래 채널의 값이 수신될때까지 대기하는 예시 코드를 바로 실행해보세요.