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
94 Remove all .git files, recursively digipine 2021.11.26 6
93 VSCode 에서 한글 특수문자 부분 만 검색하기 file digipine 2021.10.25 25
» Golang Channel 사용법 정리 digipine 2021.10.22 25
91 AWS EC2 Ubuntu 용 Docker 설치 스크립트 digipine 2021.09.01 56
90 Docker 모든 컨테이너를 Stop 또는 Remove 하는 방법 digipine 2021.09.01 45
89 Docker Compute Engine Ubuntu에서 Docker 설치 방법 lizard2019 2021.04.15 228
88 Docker에서 Phabricator 최신버전 설치 및 버전 확인 방법 file lizard2019 2021.04.15 437
87 Ubuntu 18.04 에서 vsftpd 설치하기 lizard2019 2021.03.02 203
86 우분투 18.04 MongoDB 설치 및 구성 lizard2019 2021.02.26 244
85 Let's Encrypt SSL 인증서 자동 갱신 설정 방법 digipine 2020.09.03 364
84 Certbot으로 무료 인증서 발급 받기 digipine 2020.09.03 277
83 MongoDB 설치 및 C 개발 도구 설정 1 digipine 2020.09.03 323
82 mongoose 3.8 싱글 파일 소스 코드 file digipine 2020.09.01 213
81 [iOS] Bluetooth로 App을 백그라운드 모드로 실행는 방법 lizard2019 2020.02.11 1706
80 How to Build FFMpeg for LAVFilters file lizard2019 2019.06.05 973
79 How to FFMpeg Windows Build with msys 1.0 and MinGW_64 file lizard2019 2019.06.05 1906
78 C/C++ struct 패딩(padding) 원리 이해 lizard2019 2019.03.04 1570
77 gcc thread and mutex 사용법 lizard2019 2019.02.27 905
76 윈도우즈 도스 커멘드(Command) 네트워크 관련 명령어 lizard2019 2019.02.07 827
75 Ubuntu 우분투 설치 가이드 lizard2019 2019.01.29 654
Board Pagination Prev 1 2 3 4 5 Next
/ 5