티스토리 뷰
Photo by Emile Perron on Unsplash
개요
Concurrency in Go 책의 http://aladin.kr/p/YLCKv
4장 패턴중에서 or-done-channel 을 정리해본다.
참고링크: https://jacking75.github.io/go_or-done-channel/
상황
값을 읽어서 처리할 채널 (myChan 이라고 하자)이 있고,
언제든 작업을 중단하도록 요청이 들어올 수 있는 채널 (done 이라고 하자) 이 있다고 하자.
보통의 경우는 우리는 myChan 을 통해 어떻게 데이터가 들어오거나 중단될지, done 이 어떤 타이밍에 들어올 지 알고 있지만
막연하게 myChan 과 done 채널 만을 받아서 그에 맞게 동작하도록 구현해야할 경우가 생긴다.
예를 들면, 이런 요구사항인 것이다.
1) myChan 채널을 줄께. 그러면 그 채널로 값이 들어오면 특정 작업을 해줘 2) 그런데 myChan 은 언젠가 close 될 수 있어. 그러면 당연히 작업은 끝나야 하지 3) 마지막으로 done 채널도 별도로 줄게. 언제든 이녀석이 close 되거나 값을 읽게 되면 작업을 중단해줘 |
씸플하게 for range, 하지만...
채널 (여기서는 myChan)을 통해서 들어오는 값들을 처리하는건 for range 가 무척 편리하다.
|
1) myChan 을 통해서 값이 들어올때마다 알아서 작업을 해준다. 2) 심지어는 myChan 이 close 되는지도 자동으로 알아서 loop 를 종료해준다. |
하지만 고루틴의 메모리 누수 가능성을 고려하면 별도의 done 채널을 포함한 select 문을 사용하는 것이 좋다.
- 고루틴을 생성한 놈이 고루틴의 종료까지도 챙겨야 한다는 암묵적인 원칙이 있다.
고루틴의 메모리 누수를 방지하기 위한 원칙이라고 보면 된다.
- 이를 위해 필요한 것이 아래의 done 채널이다.
|
1) done 채널이 close 되거나, 값이 들어오게 되면 즉각 루프를 빠져나오게 된다.
2) myChan 채널을 읽는데 - 우선 close 여부를 체크하여 close 되었다면 루프를 빠져나온다. - 그렇지 않다면 읽은 값으로 작업을 한다. |
고루틴을 사용하는데 있어서의 원칙
위 코드는 잘 동작하지만 뭔가 찝찝하고 지저분하다.
이런 코드가 소스 여기저기에 널려있다면 보기 좋지 않다.
이러한 패턴은 자주 쓰일 수 있으니 orDone() 이라는 함수안에 다 밀어넣어 버리자.
원칙을 짚고 넘어가자
1) Golang 에서의 동시성 코드는 가독성이 핵심이다 2) 고루틴은 마구 퍼날라 쓸 수 있는 자원으로 생각하자 → 다시말해 최적화에 대한 고민은 나중의 나중으로 미루자. → 너무 이른 최적화 (premature optimization) 를 하지 말라는 것이다. |
https://gist.github.com/nicewook/46dbb771a2755044f4d6daa45fa8abba
|
리턴값부터 보자.
1) ←chan interface{} 이다. 2) 값을 읽을 수 있거나, close 될 수 있는 채널 하나인 것이다.
orDone() 함수는 결국
1) myChan 과 done 채널을 받아서 2) 읽을 수 있는 채널 하나로 만들어 리턴해주는데
3) myChan 을 통해서 값을 읽으면 그 값을 리턴된 채널에 써서, 그 채널로 읽을 수 있게 해주고 4) myChan 이 close 되거나 done 을 받게 되면 리턴된 채널 역시 close 되게 하는 것이다.
씸플한 for range 의 귀환
드디어 우리는 orDone() 을 이용하여 씸플한 코드 + done 을 이용한 고루틴 누수방지에 성공한 것이다.
|
마지막 의문. 두 번째 select 문
왜 두 번째 select 문이 필요한걸까? 그냥 valStream ← v 만 해줘도 되지 않나?
1) valStream 은 return 되어 외부에서 읽혀질 채널이다.
2) 그런데 valStream ← v로 값을 넣어주려는데 외부에서 읽지 않고 있다면? 블락이다!
- 여기서 블락된 상태라면 두번째 <-done 이 없다면 select 문에 아무 선택지가 없는 상황이 되는 것이다. (매우 중요한 포인트)
Block 테스트 코드
- Go Playground: https://play.golang.org/p/rlqS9fSScO0
- 아래에 코드를 설명해본다.
|
repeat 함수 설명
파라미터로 done 채널과 values 슬라이스를 받는다 그리고 채널을 리턴하는데 이 채널은
1) values 슬라이스의 값들을 반복해서 리턴된 채널에 써준다 - 예를 들어 []int{1,2,3,4,5} 를 받으면 - 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, … 를 무한정 쓴다 2) 혹은 done 채널이 close 되거나 값이 들어오면 리턴된 채널을 close 해준다.
테스트에서의 역할
테스트에서는 done 채널을 close 하지 않는다. 즉, 리턴하는 채널에다 무한히 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, … 를 쓴다. |
|
orDone 함수 설명
이 함수는 이미 위에서 설명을 해두어서 생략한다. |
|
위 내용들은 repeat, orDone 함수에 대한 정의였고 이제부터 실제 테스트를 보겠다.
1) doneOrDone 채널은 orDone 함수로 넘겨진다. - 그리고 go func() 를 통해 대략 2초 뒤에 close 된다. - 다시말해 대략 2초뒤에 orDone 함수는 종료된다.
2) repeat 함수를 통해 generator 채널을 생성한다. - 이 채널은 무한정 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, …를 읽을 수 있게 된다.
이 부분이 가장 중요한 포인트이다 3) 하지만 for range 루프는 오직 두 번만 돌고 break 를 통해 튕겨나온다. - 즉, orDone 함수로 리턴되는 채널을 딱 두 번만 읽는 다는 것이다. - 이렇게 되면 orDone 함수로 리턴되는 채널에는 세번째 까지는 쓸 수 있지만 네번째 쓰기를 하려면 블락된 채널이어서 select 문에서 없는 case 나 마찬가지가 된다. |
이 코드를 돌리면 아래 오른쪽과 같은 두 가지 출력을 얻게 된다.
왼쪽 코드, orDone 함수의 핵심부를 보면서 차근히 따라가보자
1) 처음 세 번은 orDone 으로 return 되는 채널인 valStream 으로 쓰여진다.
- get data from stream and write to output stream 부분이다.
- 하지만 세번째 쓰여진 값은 읽어가지 않기에 이때부터 case valStream <- v: 는 블락이 되어 select 문에서 아예 없는 case 로 보면 된다.
2) 이 상황에서 ←c 에서 값을 읽게 되면 select 문에서는 오직 case <-done: 만을 기다리게 된다.
- 위에서 언급한대로 대략 2초뒤에 close(doneOrDone) 이 실행되면 select 문에서 통과가 된다.
- done. possibly unleash blocked code 부분이다.
- 이 다음에 ←done 을 먼저 읽는가 아니면 <-c 를 먼저 읽는 가에 따라
- done. possibly unleash blocked code 가 한 번 떠 쓰여질 수 있다.
* 써놓고 나니 두번째 <-done 에서도 바로 return 을 해줘도 될 듯 하다.
|
|
'golang' 카테고리의 다른 글
WSL2 에 Go 설치하기 (3) | 2020.09.11 |
---|---|
NoSQL 을 쓰는 이유 (0) | 2020.08.20 |
go build 의 -ldflags 옵션으로 빌드정보를 프로그램에 담아보자 (0) | 2020.07.22 |
Rob Pike의 The Laws of Reflection 블로그 포스팅 분석 (0) | 2020.02.28 |
Russ Cox 의 Interface 블로그 포스팅 분석 (0) | 2020.02.26 |
- Total
- Today
- Yesterday
- 인텔리제이
- postgres
- strange
- golang
- bun
- go
- folklore
- github
- 클린 애자일
- Gin
- 잡학툰
- solid
- notion
- OpenAI
- 제이펍
- Shortcut
- 독서후기
- 체호프
- intellij
- Bug
- JIRA
- agile
- ChatGPT
- 노션
- 2023
- pool
- websocket
- 독서
- 영화
- API
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |