Go 4. goroutine & channel

jiffydev·2021년 1월 4일
0

goroutine

goroutine은 go에서 함수를 비동기적으로 실행하여 여러 코드를 동시에 처리할 수 있도록 한다.

func main() {
	sexyCount("nico")
    sexyCount("flynn")    
}

func sexyCount(person string) {
	for i := 0; i < 10; i++ {
		fmt.Println(person, "is sexy", i)
		time.Sleep(time.Second)
	}

위 예제의 main함수 안에서 실행되는 함수는 동기적으로 실행된다. 따라서 결과는 "nico is sexy"가 10번 반복된 다음에 "flynn is sexy"가 10번 나온다.
즉, 함수를 모두 실행하는데 걸리는 시간은 20초인데, 이를 비동기적으로 동시에(concurrent) 실행하면 절반인 10초로 줄어드니, 비동기적으로 처리하는 것이 훨씬 효율적이다.
방법은 간단한데, 비동기 처리할 함수 앞에 go를 붙여주면 그것으로 비동기적으로 실행된다.

func main() {
    go sexyCount("nico")
    go sexyCount("flynn")    
}

그런데 여기서 왜 한쪽에만 go를 붙였는지 처음에는 이해하기 어렵다. 둘 다 붙여야 모든 함수가 비동기적으로 실행될 것이 아닌가?
그래서 전부 goroutine을 실행하면 이상하게도 아무것도 출력되지 않고 main함수가 종료된다.
이는 main함수와 goroutine으로 실행한 함수가 동시에 실행되기 때문인데, main함수는 모든 함수가 실행되면 종료된다. 따라서 sexyCount 함수는 실행되고 있지만 실행이 끝나기도 전에 main함수가 종료되므로 아무것도 출력되지 않는 것이다.

Channel

위와 같은 문제를 해결하기 위한 도구가 채널이다. 채널은 말그대로 비동기적으로 실행되는 함수들을 연결한 '통로'로, 고루틴 사이의 흐름을 제어하여 main 함수에 데이터를 전달하는 역할을 한다.

func main() {
	c := make(chan string)
	people := [2]string{"nico", "flynn"}
	for _, person := range people {
		go isSexy(person, c)
	}
    	fmt.Println(<-c)
	fmt.Println(<-c)
}

func isSexy(person string, c chan string) {
	time.Sleep(time.Second * 3)
	c <- person + " is sexy"
}

채널을 생성할 때는 make를 사용해 미리 생성하고, 어떤 타입으로 주고받을지를 정해놔야 한다. 그리고 실행할 함수를 정의, 실행할 때 채널을 인자로 넣어 준다.
여기서 만약에 fmt.Println(<-c)를 한 번 더 써주면 어떻게 될까?
결과는 (fatal error: all goroutines are asleep - deadlock!)과 같은 에러가 발생한다. 왜냐하면 main 함수에서는 채널을 통해 isSexy함수가 보낼 데이터를 기다리고 있지만 더이상 보낼 데이터가 없기 때문이다.

또 다른 의문은, 현재 배열의 원소만큼 채널에서 데이터를 받아 출력을 하고 있는데, 원소가 2개가 아닌 200개면 어떻게 할까?
함수를 실행해서 나온 데이터마다 채널이 필요하므로, 결과적으로 출력을 200번 해야 하는 것은 맞다. 하지만 200줄을 쓰기에는 비효율적이므로, for range를 통해 아래처럼 처리할 수 있다.

func main(){
	c := make(chan string)
	people := [5]string{"nico", "flynn","aaa","bbb","ccc"}
	for _, person := range people {
		go isSexy(person, c)

	}
	for i := 0; i < len(people); i++ {
		fmt.Println(<-c)
	}
}
profile
잘 & 열심히 살고싶은 개발자

0개의 댓글