본문 바로가기
프로그래밍/golang

concurrency in go(3)

by 나도한강뷰 2023. 1. 4.

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