package main
import "fmt"
func main() {
a := 1
b := 2
result := 0
func() {
result = a + b
}()
fmt.Println(result) // 3
}
클로저를 통해 result == 3
임을 알 수 있다.
클로저(익명 함수)도 Goroutine으로 호출할 수 있다.
package main
import "fmt"
func main() {
a := 1
b := 2
result := 0
go func() {
result = a + b
}()
fmt.Println(result) // 0
}
그런데 결과값이 3이 아닌 0이 나온다.
앞서 살펴봤듯, Goroutine이 종료되기를 기다리지 않기 때문이다.
package main
import "fmt"
func main() {
a := 1
b := 2
result := 0
go func() {
result = a + b
}()
// s1. 억지로 Goroutine 끝날때 까지 기다리기
// time.Sleep(time.Duration(1000))
// s2. fmt.Scanln으로 강제로 block
// s3. sync.WaitGroup 사용하기
fmt.Println(result) // 0
}
크게 3가지 방법으로 해결할 수 있을 것 같은데, 생각해 보면 세 방법 모두 Goroutine이 끝나기를 기다리는 것이지, Goroutine의 흐름을 제어하는건 아니다.
Channel
은 두 용도로 사용된다.
Channel은 다음과 같이 사용한다.
package main
import (
"fmt"
)
func main() {
a := 1
b := 2
result := 0
channel := make(chan int)
go func() {
channel <- a + b
}()
result = <-channel
fmt.Println(result)
}
Goroutine에서 채널에 데이터를 <-
키워드로 송신한다.
Goroutine에서 받은 데이터를 <-
키워드로 변수에 할당할 수 있다. (꼭 변수에 할당해야 하는건 아니다)
중요한 점은 Goroutine의 종료 시점이다.
package main
import (
"fmt"
)
func main() {
defer fmt.Println("main function finished")
a := 1
b := 2
result := 0
channel := make(chan int)
go func() {
defer fmt.Println("Goroutine finished")
channel <- a + b
}()
fmt.Println(result)
}
Goroutine A (main)에서 Goroutine B (익명 함수)에서 송신한 데이터를 사용하지 않고 있다.
// 실행 결과
0
main function finished
Goroutine B는 Goroutine A에서 데이터를 수신할 때 까지 기다린다.
그런데 Goroutine A에서 데이터를 수신하지 않았으므로, Goroutine A가 종료되었음에도 Goroutine B는 종료되지 않는다.
따라서 Goroutine finished
는 출력되지 않는 것이다.
다음과 같은 상황이 있다.
package main
import "fmt"
func main() {
c := make(chan string)
c <- "Hello goorm!"
fmt.Println(<-c)
}
메인 함수
에서 채널을 통해, 다른 고루틴으로 데이터를 보내려고한다.
그런데 채널을 통해 데이터를 수신하는 고루틴이 없다.
즉, 다른 고루틴에서 데이터를 수신하지 않으므로 메인 함수
고루틴은 종료되지 않고 Deadlock에 빠지게 된다.
이 예제를 통해, 송/수신을 위한 고루틴을 만들고 수신자와 송신자가 1:1 대응하지 않으면 DeadLock에 빠질 수도 있다는 사실을 알 수 있다.
그런데 고루틴에서 채널을 통해 송신하는 데이터를, 다른 고루틴에서 항상 수신하도록 하는건 불편하다.
이를 송신자, 수신자가 채널 버퍼
를 통해 데이터를 전송하도록 함으로써 해결할 수 있다.
이미지 출처: 한 눈에 끝내는 고랭 기초 - 비동기 채널과 버퍼
package main
import "fmt"
func main() {
channel := make(chan string) // 채널 생성
buffer := make(chan string, 1) // 크기가 1인 버퍼 생성
buffer <- "Hello goorm!"
fmt.Println(<-buffer) // 정상적으로 출력 후 종료된다.
}
채널 버퍼
는 다음의 규칙을 따른다.
package main
import (
"fmt"
"sync"
)
func main() {
wg := new(sync.WaitGroup)
wg.Add(1)
buffer := make(chan int, 5)
go produce(buffer)
go consume(buffer, wg)
wg.Wait()
}
func produce(buffer chan int) {
for i := 0; i < 10; i++ {
buffer <- i
fmt.Println("produce: ", i)
}
}
func consume(buffer chan int, wg *sync.WaitGroup) {
for i := 0; i < 10; i++ {
num := <-buffer
fmt.Println("consume: ", num)
}
wg.Done()
}
produce
와 consume
은 각각 별도의 고루틴으로 실행된다.그러면 동일한 상황에서, consume하는 데이터의 개수가 더 많으면 어떻게 될까?
package main
import (
"fmt"
"sync"
)
func main() {
wg := new(sync.WaitGroup)
wg.Add(1)
buffer := make(chan int, 5)
go produce(buffer)
go consume(buffer, wg)
wg.Wait()
}
func produce(buffer chan int) {
for i := 0; i < 10; i++ {
buffer <- i
fmt.Println("produce: ", i)
}
}
func consume(buffer chan int, wg *sync.WaitGroup) {
// 프로듀서는 10개만 생성하는데, 컨슈머는 100개의 데이터를 소비함
for i := 0; i < 100; i++ {
num := <-buffer
fmt.Println("consume: ", num)
}
wg.Done()
}
컨슈머는 버퍼가 비어있으면 데이터가 생길 때 까지 기다린다.
그런데 데이터는 10개밖에 생기지 않으므로 결국 consumer는 deadlock에 빠지게 된다.
따라서 송/수신 채널의 개수를 잘 맞춰야한다.
Deadlock이 발생하는 상황을 정리하자면 다음과 같다.
if Produce 개수 < Consume 개수 {
Consumer goroutine이 무한 대기
}
package main
import "fmt"
func main() {
channel := make(chan bool, 2)
channel <- true
channel <- true
fmt.Println(<-channel)
fmt.Println(<-channel)
fmt.Println(<-channel) // 비어있는 채널을 읽으면 deadlock 발생
}
위와 같은 상황에서 채널을 닫으면 어떻게 될까
package main
import "fmt"
func main() {
channel := make(chan string, 2)
channel <- "hello"
channel <- "world"
value, isOpen := <-channel
fmt.Println(value, isOpen) // hello true
value, isOpen = <-channel
fmt.Println(value, isOpen) // world true
close(channel)
value, isOpen = <-channel
fmt.Println(value, isOpen) // "" false
}
채널에 값이 없는 경우, 닫힌 채널에서 값을 읽으면 데드락이 발생하지 않고 빈 값을 반환한다.
primitive type이면 기본값
pointer 등이면 nil
닫힌 채널에 값을 넣으면 panic이 발생한다. (send on closed channel)
package main
import "fmt"
func main() {
channel := make(chan string, 2)
channel <- "hello"
channel <- "world"
for {
if value, isOpen := <-channel; isOpen {
fmt.Println(value, isOpen)
} else {
break
}
}
}
isOpen이 false가 되려면, 채널이 close되어야만 한다.
그런데 위 코드는 채널이 닫히지 않았으므로 무한 루프, 즉 Deadlock에 빠지게 된다.
그러므로 채널을 for loop돌려면 close 해야 한다.
package main
import "fmt"
func main() {
channel := make(chan string, 2)
channel <- "hello"
channel <- "world"
close(channel)
for {
if value, isOpen := <-channel; isOpen {
fmt.Println(value, isOpen)
} else {
break
}
}
}
채널에 for ragne
를 도입해서 개선할 수 있다
package main
import "fmt"
func main() {
channel := make(chan string, 2)
channel <- "hello"
channel <- "world"
close(channel)
for value := range channel {
fmt.Println(value)
}
}
마찬가지로 채널은 close되어야 한다.
채널에 for range
를 사용할 경우, isOpen
은 사용할 수 없다.
사실 채널은 방향을 정할 수 있다.
방향 = 송신, 수신 여부
기본적으로 채널은 송신, 수신이 가능한데, 목적에 따라 송신만 가능한 채널, 수신만 가능한 채널로 제한할 수 있다.
package main
func main() {
bidirectionChannel := make(chan int, 2)
bidirectionChannel <- 100 // 송신 가능
<- bidirectionChannel // 수신 가능
transChan := makeTransChan(make(chan int, 2))
transChan <- 1 // 송신만 가능
// result := <- transChan: Invalid operation: <- transChan (receive from the send-only type chan<- int)
receiveChan := makeReceiveChan(make(chan int, 2))
// receiveChan <- 1: Invalid operation: receiveChan <- 1 (send to the receive-only type <-chan int)
<- receiveChan // 수신만 가능
}
func TransOnlyChParam(ch <-chan int) {
fmt.Println(<-ch)
// ch <- 100: 수신 전용이므로 송신 불가능
}
func makeTransChan(ch chan int) chan<- int {
return ch
}
func makeReceiveChan(ch chan int) <-chan int {
ch <- 2
return ch
}
방향이 생기더라도 채널, 버퍼가 꽉 차거나, 말랐을 때 발생하는 Deadlock문제는 여전히 발생한다.
아래와 같은 코드가 있다.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan bool)
ch2 := make(chan bool)
go func() {
for {
time.Sleep(1000 * time.Millisecond)
ch1 <- true
}
}()
go func() {
for {
time.Sleep(500 * time.Millisecond)
ch2 <- true
}
}()
go func() {
for {
<-ch1
fmt.Println("ch1 수신")
<-ch2
fmt.Println("ch2 수신")
}
}()
time.Sleep(5 * time.Second)
}
두 고루틴(a, b)에서 각각 1초, 0.5초 주기로, 채널에 데이터를 전송한다.
그리고 두 채널에서 전송한 데이터를 고루틴(c)에서 수신한다.
그런데 다음과 같은 조건이 있다.
무조건 ch1를 먼저 수신한 다음에 ch2에서 데이터 수신
ch1에 데이터가 없으면 고루틴(c)이 block되어, ch2에 데이터가 더 빨리 많이 쌓이더라도ch2의 데이터를 사용하지 못하는 상황이 발생한다. (ch2도 결국 1초 주기로 데이터가 순환(?)하는 상황)
이를 Select
로 개선할 수 있다.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan bool)
ch2 := make(chan bool)
go func() {
for {
time.Sleep(1000 * time.Millisecond)
ch1 <- true
}
}()
go func() {
for {
time.Sleep(500 * time.Millisecond)
ch2 <- true
}
}()
go func() {
for {
select {
case dataFromCh1 := <-ch1: // case문에서 변수 선언이 가능하다.
fmt.Println("ch1 수신", dataFromCh1)
case <-ch2: // 변수 선언 안 해도 상관 없다.
fmt.Println("ch2 수신")
case ch1 <- bool: // case문에서 송신도 가능하다.
}
}
}()
time.Sleep(5 * time.Second)
}
select
문을 사용함으로써, ch2는 ch1의 수신 여부와 상관없이 데이터를 사용할 수 있게 된다.아래 코드는 Deadlock에 빠진다. 왜 그럴까
package main
import (
"fmt"
"sync"
)
func main() {
channel := make(chan bool)
channel <- true
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
result := <-channel
fmt.Println(result)
wg.Done()
}()
wg.Wait()
}
다음 처럼 해결할 수 있다.
package main
import (
"fmt"
"sync"
)
func main() {
channel := make(chan bool)
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
result := <-channel
fmt.Println(result)
wg.Done()
}()
channel <- true
wg.Wait()
}
또는 버퍼를 가득 채우지 않는 방식으로도 해결할 수 있을 것 같다.
package main
import (
"fmt"
"sync"
)
func main() {
channel := make(chan bool, 2)
channel <- true // 버퍼가 꽉 차지 않았으므로 블록되지 않는다.
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
result := <-channel
fmt.Println(result)
wg.Done()
}()
wg.Wait()
}
이런 상황에서 waitGroup이 없다면, 메인 함수가 익명함수를 기다리지 않고 먼저 종료될 수도 있다.