4장- go의 동시성 패턴
제한
- 동시성 프로그래밍시 안전한 작동을 만드는 옵션
- 메모리 공유를 통한 동기화 기본요소(mutex)
- 통신을 통한 동기화(channel)
- 변경불가능 데이터(const)
- 제한(confinement)에 의해 보호되는 데이터
- 제한의 2가지 방식
- ad hoc: 코드베이스에서 설정된 관례에 의해 제한이 이루어지는 경우
- lexical: 컴파일러가 제한하도록 만드는것
- critical area가 없어지므로, 동기화 비용과 같은 cost를 줄일 수 있다.
for-select 루프
for {
select{
case<-done:
return
default:
}
}
- 채널에서 반복 변수 보내기
- 멈추기를 기다리면서 무한히 대기
- 신호가 올때까지 대기하면서 무한히 반복
고루틴 누수 방지
- 고루틴은 종료되기위한 몇가지 방안이 있다.
- 작업 완료
- 복구불가 에러로 작업이 중단될 때
- 작업을 중단하라는 요청을 받았을 때
- 작업 중단이 가장 중요한 기능
- 부모 고루틴은 자식 고루틴을 중단 시킬 수 있어야된다.
- 그렇게 고안된게 읽기전용 채널 done이다.
- 자식 고루틴을 종료시킬때 done채널을 닫아버린다.
- 고루틴을 생성한 책임이 있는 고루틴은 해당 고루틴을 중지시킬 책임도 있다.
newRandStream := func(done <- chan interface{}) <-chan int {
randStream := make(chan int)
go func() {
defer fmt.Println("exited")
defer close(randStream)
for {
select {
case randStream <- rand.Int():
case <-done:
return
}
}
}() return randStream
}
라는 애를 만들고, 중간에 done channel을 close한다면, goruntine은 중단되게 된다.
or 채널
- 하나이상의 done 채널을 결합하여서, 그중 하나라도 종료되면 gorutine이 종료될 수 있도록 만드는 디자인 패턴이다.
- todo
error handling
- 에러 처리의 책임자는 누구인가?
- 단순하게 고루틴안에서 에러가 나면, 그 에러를 로그에 찍어주고 끝내는게 맞는것인가?
- 고루틴의 에러는 고루틴을 생성한 부모 고루틴에서 관리하는게 맞다.
- 자식 고루틴은 에러를 channel을 통해서 부모 고루틴으로 전달하고, 부모 고루틴이 그것을 처리하는것이 맞다.
파이프라인
- 우리는 함수, 메소드, 구조체 등의 형태를 추상화한다.
- 파이프라인도 그러한 추상화 도구중 하나이다.
- 데이터를 어떻게하면 추상화하여서 독립적으로 작동하게 만들것인가.
- 어떻게?
- 각 단계는 동일한 타입을 소비하고 리턴한다
- int slice를 받으면 int slice를 리턴한다
- 일괄 처리: 한번의 연산으로 모든 데이터 덩어리를 처리하는 것
- 스트림 처리: 한번에 하나의 요소를 수신하고 방출한다.- 확장성에 제한이 생김
- todo: 추후 정리시 예제들 추가
팬인 팬아웃
- 파이프라인을 통해서 데이터를 처리하는데, 그것의 성능을 올리는 방식이다.
- 개별 단계를 조합해서 데이터스트림에서 연산 할 수 있는 파이프라인의 특성을 이용
- 팬아웃: 파이프라인의 여러개의 입력을 처리하기위해서 여러개의 고루틴이 동시에 받아서 처리하는것
- 팬인: 여러결과를 하나의 채널로 결합하는 프로세스
- 이때 순서 독립성은 중요하다.
- goruntine특성상 어느게 먼저 실행되는지는 동기화를 하지않으면 보장되지 않기때문에.
or-done 채널
- done을 채크하는 case
- 누수 방지로 들어오는 stream이 닫혔는지 체크하는 case
- 열려있다면 그 데이터를 이용해서 하고자하는 작업을하는 case
orDone := func(done, c<-chan interface{}) <-chan interface{} {
valStream := make(chan interface{})
go func() {
defer close(valStream)
for {
select {
case <-done:
return
case v,ok := <-c:
if !ok {
return
}
select {
case valStream <- v:
case <-done:
}
}
}
}()
return valStream
}
tee 채널
- 채널을 통해서 들어오는 값을 분리해서 코드베이스의 별개의 두 영역으로 보내고자 할때 쓰는 패턴
- 유닉스 tee명령어에서 따온 이름
- todo 예시
bridge 채널
- 연속된 채널로부터 값을 사용할때 사용
- <-chan <-chan interface{}
- 아직 언제 왜 쓰는지 잘 모르겠음
대기열 사용 queuing
- 프로그램 최적화시 사용하는 마지막 기술중 하나
- 대기열 사용은 프로그램의 총 실행 속도를 높혀주지는 않는다.
- 일정 단계가 차단된 상태에 있는 시간을 줄여줄 뿐이다.
- 사용케이스
- 특정단계에서 일괄 처리 요청이 시간을 절약하는 경우
- 특정단계의 지녕으로 인해 시스템에 피드백 루프가 생성되는 경우
- 청킹
- 리틀의 법칙? todo
context패키지
- 앞서서 done채널을 통해서 고루틴을 죽이고 그랬다.
- 취소됐다는 단순한 알림 대신, 취소이유, 마감기한 등등 추가적인 정보를 전달 할 수있다면 도움이 될것이다.
- 그에 따라 go1.7에서 표준 라이브러리로 옮겨지게된 것이 context패키지
- context는 시스템 전체를 흐르는 타입이다.
- 자세한 내용 추후 정리
'프로그래밍 > golang' 카테고리의 다른 글
ultimate go (1)- syntax (0) | 2023.01.18 |
---|---|
concurrency in go(4) (0) | 2023.01.06 |
concurrency in go(2) (0) | 2023.01.03 |
concurrency in go(1) (0) | 2022.12.31 |
google go style guide 공부 (0) | 2022.12.12 |