뽀미의 개발노트

Go 디자인패턴 - Concurrency Channel Patterns (동시성 채널 패턴) 본문

카테고리 없음

Go 디자인패턴 - Concurrency Channel Patterns (동시성 채널 패턴)

산타는 뽀미 2024. 12. 19. 23:29

참고 코드 깃헙 레포

https://github.com/aQuaYi/Go-Notes/tree/10ffd4c65076d7c71a1415fb40c5fb0f11114600/temp/currency-in-Go

 

Go-Notes/temp/currency-in-Go at 10ffd4c65076d7c71a1415fb40c5fb0f11114600 · aQuaYi/Go-Notes

Go 语言笔记. Contribute to aQuaYi/Go-Notes development by creating an account on GitHub.

github.com

 

 


 

채널패턴 1) or-done channel

채널의 병합과 동시성 제어를 다루는 패턴. 실행 시간이 길거나 비정상 종료의 위험이 있는 채널 작업을 중단하여 안전하게 처리할 수 있음.

 
var orDone = func(done <-chan time.Time, c <-chan interface{}) <-chan interface{} {
	valStream := make(chan interface{})
	go func() {
		defer close(valStream)
		for {
			select {
			case <-done: // 중단 신호가 수신되면 종료
				return
			case v, ok := <-c: // `c`에서 데이터를 받음
				if !ok { // 채널이 닫힌 경우 종료
					return
				}
				select {
				case <-done: // 중단 신호 확인 (선택적으로 중단)
					return
				case valStream <- v: // 받은 데이터를 `valStream`에 전달
				}
			}
		}
	}()
	return valStream
}

done은 중단 채널. 작업 중단 여부를 확인하여 done에서 신호를 받으면 반복문을 종료시킴.  c에서 데이터를 받았고 + 중단 신호 없는 경우 valStream 채널에 전달함.

 
func main() {
	unstableChan := makeSleepChan() // 불안정한 채널 생성
	for v := range orDone(
		time.After(time.Second*12), // 12초 타이머 채널
		unstableChan,
	) {
		fmt.Printf("%v ", v) // 데이터를 수신하여 출력
	}
	fmt.Println()
}

makeSleepChan()로 비정상적으로 길게 실행될 수도 있는 불안정한 채널을 시뮬레이션 함. 12초 후에 닫히는 채널 (12초 후 done 신호 역할을 함.) 12초가 지났거나 채널이 닫히면 루프 종료됨.

done 채널로 작업 중단 조건(12초 내)을 추가하여 불필요한 대기 상태 방지함. 채널이 닫히거나 중단 신호가 오면 orDone 내부 루틴이 종료되어 채널과 고루틴의 안전한 종료를 보장함(리소스 누수 방지)

 

채널패턴 2) tee channel

tee는 T자형 파이프에서 유래한 이름. 하나의 입력 채널을 복제하여 여러 출력 채널로 데이터를 동시에 전달하는 방식. 동시성 프로그래밍에서 데이터 스트림을 여러 consumer가 독립적으로 처리할 수 있도록 하기에 적합.

 
var tee = func(
	done <-chan interface{}, // 작업 중단 신호를 수신하는 채널
	in <-chan interface{}, // 입력 채널
) (_, _ <-chan interface{}) { // 두 개의 출력 채널 반환
	out1 := make(chan interface{})
	out2 := make(chan interface{})
	go func() {
		defer close(out1)
		defer close(out2)
		for val := range orDone(done, in) { // `orDone`로 안전한 데이터 수신
			o1, o2 := out1, out2
			// 두 출력 채널로 값을 보냄
			for i := 0; i < 2; i++ {
				select {
				case <-done: // 중단 신호가 오면 종료
					return
				case o1 <- val: // 첫 번째 채널로 전송
					o1 = nil
				case o2 <- val: // 두 번째 채널로 전송
					o2 = nil
				}
			}
		}
	}()
	return out1, out2
}

in 채널에서 데이터를 수신하여 out1, out2 두 출력 채널로 데이터를 복제함. 전송 후 채널 nil로 초기화하여 다시 전송하지 않도록 방지함. done 채널로 중단 신호를 받으면 즉시 작업 종료하도록 함.

 
func main() {
	done := make(chan interface{}) // 중단 채널 생성
	defer close(done)

	var wg sync.WaitGroup

	// tee 패턴으로 입력 채널을 두 개의 출력 채널로 분리
	out1, out2 := tee(done, makeIntChan())

	wg.Add(2) // 두 개의 고루틴 추가

	// 두 출력 채널에서 데이터를 수신하여 출력
	go output("1", out1)
	go output("2", out2)

	wg.Wait() // 모든 작업이 완료될 때까지 대기

	fmt.Println("Done")
}

makeIntChan()은 0부터 11까지 데이터를 생성하는 입력 채널임. tee로 입력 채널을 두 개의 출력 채널로 분리함. 각각의 출력 채널에서 데이터를 출력하는 고루틴 실행함.

티 채널 패턴의 장점

데이터를 안전하게 두 곳 이상으로 분기할 수 있음. 한 채널에서는 데이터를 로깅하고, 다른 채널에서 실제 작업을 처리하도록 하는 등 유연하게 할 수 있음.