이전에 작성했던 글의 마지막 부분인 Routines
과 Channels
에 대해 추가로 정리
[IT/Go] - `쉽고 빠른 Go 시작하기` 노마드코더 강의 정리 (Golang)
Go Routines
일반적으로 프로그램은 동시에 진행되지 않는다. 위에서 아래로 흐를 뿐
예를들면,
func main() {
checkCount("jeffrey")
checkCount("oh")
}
func checkCount(word string) {
for i:=0; i<10; i++ {
fmt.Println("Welcome ! ", word)
time.Sleep(time.Second) // time 함수를 이용하여 1초씩 지연
}
}
그 결과 첫 번째 checkCount("jeffrey")
가 먼저 10번을 출력하고 끝나면 checkCount("oh")
가 10번을 출력한다.
jeffrey 0
jeffrey 1
jeffrey 2
jeffrey 3
jeffrey 4
jeffrey 5
jeffrey 6
jeffrey 7
jeffrey 8
jeffrey 9
oh 0
oh 1
oh 2
oh 3
oh 4
oh 5
oh 6
oh 7
oh 8
oh 9
함수 2번 실행하는데 무려 20초가 필요하다. 전혀 도움이 되지 않는다. 이럴 때 사용하는게 goroutines
이다. 사용법은 아주 간단하다. 그저 실행할 함수 앞에 go
만 추가해주면 된다.
func main() {
go checkCount("jeffrey")
checkCount("oh")
}
그러면 jeffrey
를 출력하는 것과 oh
를 출력하는게 동시에 실행되어 10초면 2개의 함수가 종료된다.
결과 콘솔에는 다음처럼 나온다.
oh 0
jeffrey 0
jeffrey 1
oh 1
oh 2
jeffrey 2
jeffrey 3
oh 3
jeffrey 4
oh 4
oh 5
jeffrey 5
oh 6
jeffrey 6
oh 7
jeffrey 7
jeffrey 8
oh 8
jeffrey 9
oh 9
동시에 실행되고 있다는 점이 확실히 보인다. 그러면 2개다 go
를 붙이면 어떻게 될까 ?
func main() {
go checkCount("jeffrey")
go checkCount("oh")
}
둘 다 goroutines
를 실행했기 때문에 main함수는 다음으로 실행될 코드가 없어서 종료가 된다. 이 때, 실행 중이던 goroutines
들도 자동적으로 종료된다. main이 기다려주지 않는다.
그러면 main 함수와 goroutines
가 서로 커뮤니케이션을 하려면 어떻게 해야할까 ?
정답은 Channels
이다.
Channels
우리는 앞서 다뤘듯이 main 함수가 종료되면 진행중이던 goroutines
가 종료된다는 것을 확인했다.
func main() {
people := [2]string{"jeffrey", "oh"}
for _, person := range people {
go isHuman(person)
}
}
func isHuman(person string) bool {
time.Sleep(time.Second * 5)
return true
}
위 소스는 실행은 되지만 반응이 없다. 하지만 isHuman
이라는 함수는 true
라는 논리값을 리턴한다. 그렇다고 다음처럼 할 수는 없다.
result := go isHuman(person) // 틀린 소스
Channels
을 만드는 방법은 쉽다. 다음처럼 make
함수를 이용하여 chan
을 선언하면된다.
c := make(chan bool) // isHuman 이 bool을 return
Channels
가 추가된 소스는 다음과 같다.
func main() {
c := make(chan bool)
people := [2]string{"jeffrey", "oh"}
for _, person := range people {
go isHuman(person, c)
}
}
func isHuman(person string, c chan bool) {
time.Sleep(time.Second * 5)
c <- true // 채널을 이용하여 결과를 알려줌
}
하지만 결과를 출력하면 main 함수가 끝나버려서 출력되지 않는다. 그렇다고 for문 아래에 시간을 10초지연시킨다고 출력되지 않는다. 그러면 메시지는 보내진게 아닌가 ? 라는 의문이 들텐데 메시지는 확실히 보내졌다. 그것을 알아보기 위해 다음 소스를 추가한다.
func main() {
c := make(chan bool)
people := [2]string{"jeffrey", "oh"}
for _, person := range people {
go isHuman(person, c)
}
// 추가
result := <-c
fmt.Println(result)
}
func isHuman(person string, c chan bool) {
time.Sleep(time.Second * 5)
c <- true // 채널을 이용하여 결과를 알려줌
}
그 결과 true
가 출력될 것이다. main 함수는 channels
로 부터 결과를 받아야하는 경우에는 결과가 나올 때까지 기다렸다가 다음을 실행한다. 그러므로 위 소스는 main함수가 실행 도중 result := <- c
부분에서 잠시 멈춰있다.
2번이 실행되니 2개의 결과를 받으려면 미리 result에 값을 받지 않고 다음 처럼 하면된다.
// result := <-c // 이 부분을
fmt.Println(<-c)
fmt.Println(<-c)
그 결과 true
가 2번이 출력될 것이고 누가 먼저 끝났는지 보기 위해 체크해보려면
func isHuman(person string, c chan bool) {
time.Sleep(time.Second * 5)
fmt.Println(person) // 이 부분을 추가하여 체크
c <- true // 채널을 이용하여 결과를 알려줌
}
위 처럼 변경해보면 알 수 있을 것이다. 그러면 fmt.Println(<-c)
가 2번이니까 3번도 되는거 아니냐고 의문이 생길 수 있는데 3번을 출력하면 데드락이 발생한다.
fatal error: all goroutines are asleep - deadlock!
라는 메시지가 출력되면서 에러가 발생한다. 그러니 2번 출력했다고 true
가 2번나온게 아니라는 개념이다.
<-c
처럼 메시지를 기다리는 것을 blocking operation
이라고 한다.
우리가 받을 수 있는 메시지가 2개라는 것을 확실히 안다면 다음처럼 확인해 볼 수 있다.
func main() {
c := make(chan string) // type string으로 수정
people := [2]string{"jeffrey", "oh"}
for _, person := range people {
go isHuman(person, c)
}
fmt.Println("Waiting for messages")
resultOne := <-c
resultTwo := <-c
fmt.Println("Received this message:", resultOne)
fmt.Println("Received this message:", resultTwo)
}
func isHuman(person string, c chan string) { // type string으로 수정
time.Sleep(time.Second * 2)
c <- person + " is awesome"
}
논리값이 아닌 문자열을 찍어보기위해 약간 수정하였다. 근데 저렇게 하면 많은 값을 받을 땐 역시 힘들다. 따라서 받을 메시지도 반복문을 이용하여 찍어본다.
func main() {
c := make(chan string) // type string으로 수정
people := [2]string{"jeffrey", "oh"}
for _, person := range people {
go isHuman(person, c)
}
for i := 0; i< len(people); i++ {
fmt.Println(<-c)
}
}
func isHuman(person string, c chan string) { // type string으로 수정
time.Sleep(time.Second * 2)
c <- person + " is awesome"
}
추가적으로 말하자면 channels
는 보내기와 받는게 가능한데 보내기만 가능하게끔 설정하려면 다음처럼 하면된다.
func isHuman(person string, c chan<- string) { // chan 에 <- 추가하면
time.Sleep(time.Second * 2)
// fmt.Println(<-c) // 여기서는 메시지를 받을 수 없다
c <- person + " is awesome" // 보내는 것만 가능
}
Rule
goroutines
와 channels
를 사용할 땐 약간의 룰을 지키도록 하자.
- main 함수가 끝나기 전에 실행되어야한다.
- 받을 데이터, 채널로 보낼 데이터에 대해 어떤 타입인지 구체적으로 지정한다.
'Backend > Go' 카테고리의 다른 글
네이버 금융 삼성전자 주가 및 투자자별 매매동향 크롤링 (1) | 2021.01.19 |
---|---|
`쉽고 빠른 Go 시작하기` 노마드코더 강의 정리 (Golang) (3) | 2021.01.15 |