[
go
]
Go의 동시성
go의 동시성 모델은 CSP(Communicating Sequential Processes, 순차적 프로세스들의 통신)을 기반으로 함
주의 사항
- 동시성(concurrency)은 병렬성(parallelism)이 아니다
- 동시성은 동시에 실행되는 것이 시간이 얼마 걸리지 않을 때 사용하는 것은 좋지 않다
고루틴
- 프로세스: 컴퓨터의 운영체제에서 수행 중인 프로그램의 인스턴스
- 스레드: 운영체제가 주어진 시간동안 수행되는 실행의 단위
- 프로세스는 하나 이상의 스레드로 구성된다.
- 고루틴은 Go 런타입에서 관리하는 가벼운 프로세스
- 운영체제 레벨 자원을 생성하지 않기 떄문에 스레드 생성보다 빠름
- 초기 스택 크기는 스레드의 스택 크기 보다 작음. 메모리 효율적 사용
- 스레드 사이 전환히 프로세스 내에서 일어나서 느린 운영체제 시스템 호출을 회피함
- 스케줄러가 결정을 최적화
채널
- 고루틴은 채널을 통해 통신한다.
- 참도 타입 -> 함수로 전달하면 채널에 대한 포인터를 전달
읽기, 쓰기, 버퍼
a := <-ch // ch에서 값을 읽어 a에 할당
ch <- b // b의 값을 ch에 씀
- 다중 고루틴이 같은 채널에서 읽기를 한다면 채널에 쓰인 하나의 값은 다중 고루틴 중 하나만 읽을 수 있다.
- 읽기 전용:
ch <-chan int
- 쓰기 전용:
ch chan<- int
- 기본적으로 채널은 버퍼가 없다
- 채널에 쓰기를 하면 다른 고루틴에서 채널을 읽어줄 때까지 일시 중단
- 채널에 읽기를 하면 다른 고루틴에서 채널을 써줄 때까지 일시 중단
- 고루틴간에 같이 움직이도록 설계됨
더 알아보기
- 고루틴이 오랫동안 블로킹 상태여도 시스템적으로는 문제되지 않음.
- 다만 상대가 영원히 안 나타나면 deadlock이 발생할 수 있음.
- 그래서 채널을 사용할 땐 항상 누가 언제 받거나 보낼지를 잘 설계해야 해.
for-range
for v := rance ch {
fmt.Println(v)
}
- 채널이 닫히거나, 루프 탈출을 지정해주기 전까지 지속됨
채널 닫기
- close 내장 함수를 이용하여 채널을 닫음
v, ok := <-ch
콤마 OK 관용구를 사용하여 담힘 유무 확인- 채널에 쓰기를 하는 고루틴에서 책임
- 닫혀지기를 기다리는 고루틴이 있는 경우만 필요 (for-range)
select 문
- 기아(starvation): 어떤 하나는 다른 것보다 더 선호하거나 어떤 경우를 아예 처리하지 않을 수 없다.
- select는 여러 채널의 세트 중 하나에 읽기를 하거나 쓰기를 할 수 있는 고룬틴을 허용
select {
case v := <-ch:
fmt:Println(v)
case v := <-ch2:
fmt.Println(v)
case ch3 <- x:
fmt.Println("wrote", x)
case <-ch4:
fmt.Println("got value on ch4, but ignored it")
}
- 여러 case에 읽거나 쓸 수 있는 채널이 있으면 임의로 선택됨 -> 기아 문제 해결
- select로 채널을 접근하여 deadlock을 피함
동시성 사례와 패턴
func main() {
a := []int{2, 4, 6, 8, 10}
ch := make(chan int, len(a))
for _, v := range a {
go func() {
ch <- v * 2
}()
}
}
- 모든 고루틴을 위한 클로저가 같은 변수를 캡처함
for_, v := range a {
go func(val int) {
ch <- val * 2
}(v)
}
- 고루팀의 값이 변경될 수 있는 변수를 사용할 때마다, 변수의 현재 값을 고루틴으로 넘겨주도록 하자.
- 고루틴을 종료하지 않으면 스케줄러가 시간을 할당하여 프로그램이 느려지게됨 -> 누수(leak)
- Done 채널 패턴
- 빈 구조체의 done이라는 채널을 사용하여 종료해야 하는 시점을 알려줌
- 취소 함수 사용
- done 채널을 닫는 클로저를 생성하여 반환
- 버퍼가 있는 채널은 얼마나 많은 고루틴이 실행될 지를 알고 있을 때, 실행시킬 고루틴의 개수를 제한하거나 대기 중인 작업의 양을 제한하려는 경우 유용하다.
배압
- 배압(backpressure): 버퍼가 있는 채널을 사용하고 select 문으로 시스템에 동시에 들어오는 요청의 수를 제한할 수 있다.
select에서 case 문 해제
for {
select {
case v, ok := <-in:
if !ok {
in = nil // 해당 case는 더 이상 성공할 수 없다!
continue
}
// in에서 읽은 값을 v로 처리한다.
case v, ok := <-in2:
if !ok {
in2 = nill // 해당 case는 더 이상 성공 할 수 없다!
continue
}
// in2에서 읽은 값을 v로 처리
case <-done:
return
}
}
WaitGroup의 사용
- 고루틴을 설계하때 첫 번째 선택이 되어서는 안됨
- 고루틴이 종료하고 나서 정리할 무엇인가 있는 경우에만 사용