c := make(chan string)
c <- "Hello"
<-c // 오류 발생: fatal error: all goroutines are asleep - deadlock!
위와 같은 경우, 코드가 순차적으로 실행된다.
2번째 라인이 실행된 후에 3번째 라인이 실행되는데,
2번째 라인에서 send하려는 시점에 3번째 라인에서 receive할 준비가 되지 않아서 보낼 수가 없다.
c := make(chan string, 1)
c <- "Hello"
fmt.Println(<-c) // 출력: Hello
버퍼가 있는 채널이라면 정상적으로 동작할 수 있다.
send하려는 시점에 receive할 준비는 되어있지 않지만, 버퍼에 넣어두면 되니까 괜찮다.
c := make(chan string)
go func() {
c <- "Hello"
}()
fmt.Println(<-c) // 출력: Hello
정상적으로 실행된다. 왜 가능할까?
고루틴은 스케줄링되어 실행되기 때문이다.
실행 순서
1. 고루틴이 스케줄링 된다.
2. <-c에 왔지만 send된 데이터가 없어서 대기
3. 어느 시점에 고루틴이 실행된다. c <- "Hello"
4. fmt.Println(<-c)
channel에서 데이터를 receive 받는 방법으로 range가 있다. 이때, range는 channel이 close되어야 끝난다. 고로, channel을 close해주지 않는다면 영원히 기다리면서 deadlock이 발생한다.
ch := make(chan int, 1)
ch <- 101
for value := range ch {
fmt.Println(value)
}
Output:
101
fatal error: all goroutines are asleep - deadlock!
101을 보낸 것은 잘 받아서 처리했지만, 그 후에 close되지 않아서 deadlock이 발생한다.
ch := make(chan int)
close(ch)
ch <- 1 // panic: send on closed channel
ch := make(chan int, 2)
var wg sync.WaitGroup
wg.Add(1)
go func() {
ch <- 10
ch <- 11
wg.Done()
}()
wg.Wait()
close(ch)
fmt.Println(<-ch) // 10
fmt.Println(<-ch) // 11
fmt.Println(<-ch) // 0
waitGroup에 대해 잘 모르는 분이라면...
버퍼가 2개인 채널에 10, 11을 send할 때까지 기다린 후에,
channel을 close하고 receive하는 코드라고 이해하자.
close된 channel에 send 할 수는 없지만 receive할 수는 있다.
버퍼에 저장된 값은 고대로 읽어오고 값이 없다면 zero value를 가져온다.
select는 무한루프가 아니기 때문에 for-select 형식으로 사용된다.
하지만, case가 발생할 때까지 대기한다.
ch := make(chan int)
takeSomeTime := func() {
go func() {
time.Sleep(time.Second * 2)
ch <- 1
}()
}
start := time.Now()
takeSomeTime()
select {
case <-ch:
fmt.Println(time.Since(start)) // 2.004948752s
}
takeSometime은 2초간 대기한 후에 채널로 데이터를 보내는 함수이다. 요 데이터를 보내야 case는 값을 receive할 수 있다. 만약 select문에서 <-ch 발생을 기다리지 않고 넘어간다면 Output이 출력되지 않았을 것이다. 하지만 <-ch이 발생할 때까지 기다렸기 때문에 대략 2초 후에 출력되었다.
default가 있다면 고놈을 실행한다.
select에서 case는 무작위로 실행되며, 각 case는 모두 비슷한 확률로 실행된다. (균일한 의사 무작위 선택)
c1 := make(chan interface{})
close(c1)
c2 := make(chan interface{})
close(c2)
var c1Count, c2Count int
for i := 1000; i >= 0; i-- {
select {
case <-c1:
c1Count++
case <-c2:
c2Count++
}
}
fmt.Printf("c1Count: %d\nc2Count: %d\n", c1Count, c2Count)
Output
c1Count: 519
c2Count: 482
대략 비슷하게 case가 실행된다.
(아까 위에서 언급했듯이, close해도 receive할 수 있기 때문에 위의 예는 문제가 없이 동작한다.)
select {
case <-ch:
case ch <- doSomething:
}
channel로 값이 오는 것을 기다리는 것 외에도 channel에 값을 전달하는 식으로도 select문을 이용할 수 있다. 이때, doSoemthing이 끝날 때까지 다른 case는 검사하지 않는다.
longFunction := func() interface{} {
defer fmt.Println("end long function")
fmt.Println("start long function")
time.Sleep(time.Second)
return nil
}
shortFunction := func() interface{} {
defer fmt.Println("end short function")
fmt.Println("start short function")
return nil
}
chan1 := make(chan interface{},10)
chan2 := make(chan interface{},10)
for i := 0; i < 10; i++ {
select {
case chan1 <- longFunction():
case chan2 <- shortFunction():
}
}
start long function
end long function
start short function
end short function
start long function
end long function
start short function
end short function
start long function
...
소요시간이 긴 long function이 끝난 후에 다른 case의 short function이 실행되는 것을 볼 수 있다. select case에서 값을 send하는 경우에는 말 그대로 해당 case line이 실행된다고 이해하면 되겠다.
select {
case chan1 <- funcA():
case chan2 <- funcB():
}
위와 같은 상황에서 funcA를 먼저 처리한다고 가정하자. 고러면 funcA 함수는 끝까지 실행된다. 그런데 끝까지 실행하고 보니 chan1이 데이터를 받지 못하는 상황(버퍼가 꽉찬 채널)일 수 있다. 그러면 funcB()로 차례가 넘어간다.
chan1 := make(chan interface{})
chan2 := make(chan interface{},10)
functionA := func() interface{} {
defer fmt.Println("end a function")
fmt.Println("start a function")
return nil
}
functionB := func() interface{} {
defer fmt.Println("end b function")
fmt.Println("start b function")
return nil
}
for i := 0; i < 10; i++ {
select {
case chan1 <- functionA():
fmt.Println("case A running")
case chan2 <- functionB():
fmt.Println("case B running")
}
}
위 예제를 보면 channel에 데이터를 보내기는 하지만 받는 부분이 없다. ch1의 경우는 버퍼가 없기 때문에 데이터를 보낼 수 없고, ch2의 경우에는 버퍼가 넉넉하게 있기 때문에 데이터를 보낼 수 있다. 위 코드를 실행하면 다음과 같은 결과가 나온다.
start a function
end a function
start b function
end b function
case B running
start a function
end a function
start b function
end b function
case B running
case문에 있는 funtionA(), functionB() 모두 실행되지만 실질적으로 채널에 데이터를 보낸 것은 functionB()만 가능하다.