Programming Language/go
Go 언어 프로그래밍 - 채널과 컨텍스트
김크리
2021. 8. 9. 23:10
『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트입니다.
채널
채널은 고루틴간 메시지 큐이다.(FIFO) 고루틴끼리 메시지를 전달할 수 있도록 한다. Thread-safe queue 라고도 한다.multi thread 환경에서 lock 없이 사용할 수 있는 큐이다.
사용방법
//1. make()로 채널 인스턴스 생성
var messages chan string = make(chan string)
//chan 은 channel 키워드
//2. 채널에 데이터 넣기
messages <- "This is a message"
//<- 연산자를 이용하여 채널 인스턴스에 데이터를 삽입
//3. 채널에서 데이터 빼기
var msg string = <- messages
//<- 연산자를 이용하여 채널 인스턴스에서 뺀 데이터를 변수에 삽입
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
//main 이 아닌 go루틴
//lock 필요없이 채널 간 데이터 공유
go square(&wg, ch)
ch <- 9
wg.Wait()
}
func square(wg *sync.WaitGroup, ch chan int) {
n := <-ch
time.Sleep(time.Second)
fmt.Println("Square : ", n*n)
wg.Done()
}
채널의 크기
기본 크기는 0이다.
package main
import (
"fmt"
"time"
)
func main() {
//0개짜리 채널생성(기본값이 0)
ch := make(chan int)
go square()
//채널에 값을 계속 넣음...근데 채널의 값을 가져가는 것이 없다.
//채널 값을 가져가는 것이 없어 대기중
//무한대기
ch <- 9
//채널 무한대기로 출력안됌
fmt.Println("Never print")
}
func square() {
for {
time.Sleep(2 * time.Second)
fmt.Println("sleep")
}
}
채널에서 데이터대기
데이터를 계속 읽어오는 경우가 발생 한다. 이럴경우 데드락이 발생한다.
좀비 고루틴 : 채널을 닫아주지 않아서 무한 대기를 하는 고루틴을 좀비 고루틴/고루틴 릭(leak)이라고 한다.
이런 경우 close로 채널을 닫아준다.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go square(&wg, ch)
for i := 1; i < 10; i++ {
ch <- i * 2
}
//3. 좀비고루틴을 막기 위해 채널을 닫아줌
close(ch)
wg.Wait() //1.메인 고루틴의 중단
}
func square(wg *sync.WaitGroup, ch chan int) {
//2.무한하게 데이터를 채널에서 가져오려고 대기
for n := range ch {
fmt.Println("Square : ", n*n)
time.Sleep(time.Second)
}
wg.Done()
}
//데드락 발생
select문
여러 채널에서 동시에 데이터를 기다릴때 사용한다. switch 문과 비슷하게 생겼지만 다르다.
select {
case n:= <-ch1
...
case n2:= <-ch1
...
case ...
}
select 문은 일정 간격으로 실행한다.
package main
import (
"fmt"
"sync"
"time"
)
/*
time 패키지의 tick()은 일정 간적으로 신호를 주는 채널을 반환한다.
After()는 일정 시간 대기 후 한번만 신호를 주는 채널을 반환한다.
*/
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go square(&wg, ch)
for i := 1; i < 10; i++ {
ch <- i * 2
}
close(ch)
wg.Wait() //메인 고루틴의 중단
}
func square(wg *sync.WaitGroup, ch chan int) {
tick := time.Tick(time.Second) //1초에 한번씩 신호가 옴
terminate := time.After(10 * time.Second) //10초에 한번씩 채널 반환
for {
select {
case <-tick: //tick에서 데이터를 뽑을 수 있는 경우,
fmt.Println("tick")
case <-terminate:
fmt.Println("Terminated")
wg.Done() //10초 지나면 프로그램 종료
return
case n := <-ch:
fmt.Println("Square : ", n*n) //채널에 들어온 데이터 출력
time.Sleep(time.Second)
}
}
wg.Done()
}
채널로 생산자/소비자 패턴 구현
자원을 관리하기 위해 역할을 나누는 방법으로 생산자(producer)/소비자(consumer) 패턴이 있다. 예제 참고(ex25.7.go)
컨텍스트(Context)
작업을 지시할 때 작업 가능 시간, 작업 취소 등의 조건을 지시할 수 있는 작업 명세서 역할을 한다.
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
//취소 가능한 컨텍스트 생성
ctx, cancel := context.WithCancel(context.Background())
go PrintEverySecond(ctx)
time.Sleep(5 * time.Second)
cancel()
wg.Wait()
}
func PrintEverySecond(ctx context.Context) {
tick := time.Tick(time.Second)
for {
select {
case <-ctx.Done(): //작업이 끝날때 신호가 온다.
wg.Done()
return
case <-tick:
fmt.Println("tick")
}
}
}
특정 값 설정
package main
import (
"context"
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
//어떤 데이터를 지정할 수 있다.
ctx := context.WithValue(context.Background(), "number", 9)
go square(ctx)
wg.Wait()
}
func square(ctx context.Context) {
if v := ctx.Value("number"); v != nil {
n := v.(int)
fmt.Printf("Square : %d", n*n)
}
wg.Done()
}
컨텍스트 래핑
컨텍스트를 만들때 래핑하여 어러가지 긴으을 만들 수 있다.
채널로 발행(Publisher)/구독(Subscriber) 패턴 구현
옵져버 패턴과 거의 유사하다.
옵져버 패턴이란, 어떠한 이벤트 subject 가 있다면 해당 이벤트가 언제 끝나는지 감시한다. subject 가 이벤트가 발생함에 따라 notify를 준다. 이러한 패턴이 옵저버 패턴이다.
옵저버 패턴은 기본적으로 동기식이다. 이때 동기로 인해 발생하는 문제를 해결하기 위해서 pub/sub 패턴이 확장되어 나왔다.