logo

English

이곳의 프로그래밍관련 정보와 소스는 마음대로 활용하셔도 좋습니다. 다만 쓰시기 전에 통보 정도는 해주시는 것이 예의 일것 같습니다. 질문이나 오류 수정은 siseong@gmail.com 으로 주세요. 감사합니다.

Golang Channel 사용법 정리

by digipine posted Oct 22, 2021
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print

 

channel에 send할 때는 receive할 준비가 되어 있어야 한다.

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)


range는 channel이 close되어야 끝난다.

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이 발생한다.


close된 channel에 send 할 수 없다.

ch := make(chan int)
close(ch)
ch <- 1 // panic: send on closed channel

close된 channel에서 receive 할 수 있다.

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를 가져온다.


nil 채널에 send/receive하면 영구 대기


select

select문은 무한루프가 아니지만, case가 올 때까지 기다린다.

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는 순차적으로 실행되지 않는다.

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할 수 있기 때문에 위의 예는 문제가 없이 동작한다.)

case에 함수가 있다면 그 함수가 끝난 후, 다른 case를 검사한다.

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이 실행된다고 이해하면 되겠다.


case에 있는 함수는 끝까지 실행하지만, 적절하지 않은 상황이라면 다른 case를 실행한다.

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()만 가능하다.

TAG •

List of Articles
No. Subject Author Date Views
95 Docker 모든 컨테이너를 Stop 또는 Remove 하는 방법 digipine 2021.09.01 103
94 Remove all .git files, recursively digipine 2021.11.26 187
93 Phabricator Ubuntu Installation Guide digipine 2022.01.26 190
92 Git Commnd 사용법 정리 digipine 2017.11.02 207
91 AWS EC2 Ubuntu 용 Docker 설치 스크립트 digipine 2021.09.01 213
90 WPA_SUPPLICANT 빌드 방법 digipine 2017.11.01 237
89 Git 서버 구축 - 우분투[Ubuntu] digipine 2017.11.02 241
88 Ubuntu 18.04 에서 vsftpd 설치하기 lizard2019 2021.03.02 246
» Golang Channel 사용법 정리 digipine 2021.10.22 247
86 mongoose 3.8 싱글 파일 소스 코드 file digipine 2020.09.01 251
85 Compile FFmpeg on Ubuntu, Debian, or Mint digipine 2017.11.02 252
84 Windows API - 안전한 문자열 함수들 digipine 2017.10.28 256
83 JDK Install ubuntu digipine 2017.11.02 257
82 ALM의 등장 배경, 오해와 진실 digipine 2017.10.28 269
81 Docker Compute Engine Ubuntu에서 Docker 설치 방법 lizard2019 2021.04.15 298
80 소프트웨어 테스팅 전문가들을 위한 사이트 digipine 2017.11.02 301
79 Ubuntu Git - Latest Version Install digipine 2017.11.02 302
78 Mac Address 를 String 으로 변환하는 간편한 방법 digipine 2017.11.02 313
77 윈도우 한영 전환 쉬프트 스페이스로 변경 digipine 2017.11.03 314
76 우분투 18.04 MongoDB 설치 및 구성 lizard2019 2021.02.26 325
Board Pagination Prev 1 2 3 4 5 Next
/ 5