뽀미의 개발노트

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

아키텍처

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

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

코드 참고 링크

https://github.com/gobenpark/go-design-pattern

 

GitHub - gobenpark/go-design-pattern

Contribute to gobenpark/go-design-pattern development by creating an account on GitHub.

github.com

패턴 설명 참고 링크

https://refactoring.guru/ko/design-patterns/what-is-pattern

 

디자인 패턴이란?

디자인 패턴이란? 디자인 패턴은 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 전형적인 해결책입니다. 이는 코드에서 반복되는 디자인 문제들을 해결하기 위해 맞춤화할 수 있는

refactoring.guru

 


 

동시성패턴 1) Barrier Pattern

동시성 프로그래밍에서 사용되는 패턴으로, 여러 작업을 병렬로 수행한 뒤 모든 작업이 완료되거나 특정 조건이 충족될 때까지 기다리는 방식. 여러 작업의 결과를 모아 처리하거나, 작업 중 하나라도 실패하면 즉시 종료하는 시나리오에 적합함. 여러 요청을 병렬로 실행하기 위해 고루틴을 사용함. 요청 결과를 채널로 수집하여 각 작업이 완료될 때까지 기다림. 요청중 일부가 실패하더라도 프로그램 전체가 중단되지는 않도록 설계함. time.Duration을 활용하여 요청시 시간이 초과되지 않도록 관리할 수 있음.

작업간 독립성을 유지하면서도 모든 결과를 기다리고 처리할 수 있음. 병렬 처리를 효율적으로 하고, 에러 처리를 안정적으로 구현할 수 있음.

 

동시성패턴 2) Future Pattern

비동기 작업의 결과를 나중에 사용할 수 있도록 약속(Promise)을 제공하는 패턴. 작업이 완료되지 않아도 해당 결과를 표현하는 객체(future)를 반환하고 실제 결과는 작업이 완료되면 비동기적으로 설정됨.

 
type MaybeString struct {
	successFunc SuccessFunc
	failFunc    FailFunc
}

여기서 MaybeString이 future를 표현하는 구조체. 내부에 성공시 실행할 함수 successFunc와 실패시 실행할 함수 failFunc를 저장해놓음. 

 
func (s *MaybeString) Execute(f ExecuteStringFunc) {
	go func(s *MaybeString) {
		str, err := f()
		if err != nil {
			s.failFunc(err)
		} else {
			s.successFunc(str)
		}
	}(s)
}

비동기 작업을 수행하는 메서드. 고루틴에서 실행하며 함수 f를 호출하여 작업의 결과를 얻음. 결과가 성공적으로 리턴되면 successFunc를 호출하고 에러가 발생하면 failFunc 호출함.

활용 예시

 
package main

import (
	"errors"
	"fmt"
	"future"
)

func main() {
	// Future 객체 생성
	future := &future.MaybeString{}

	// 성공 콜백과 실패 콜백 설정
	future.
		Success(func(result string) {
			fmt.Println("Success:", result)
		}).
		Fail(func(err error) {
			fmt.Println("Failed:", err)
		}).
		// 비동기 작업 실행
		Execute(func() (string, error) {
			// 작업 시뮬레이션: 성공 또는 실패 조건 설정
			if true { // 성공 조건
				return "Hello, Future!", nil
			} else { // 실패 조건
				return "", errors.New("something went wrong")
			}
		})

	// 메인 루프가 종료되지 않도록 대기 (테스트용)
	fmt.Scanln()
}

Success 메서드로 성공 시 실행할 동작 정의, Fail 메서드로 실패 시 실행할 동작 정의. Execute 메서드로 비동기 작업 실행함. 작업 결과는 고루틴 내부에서 처리되고 성공 또는 실패에 따라 적절한 콜백 호출됨.

퓨처 패턴의 장점

작업 완료시 콜백을 통해 간단히 결과를 처리할 수 있음. 체이닝을 사용하여 콜백 등록과 작업 실행을 명확히 구분 가능. 고루틴을 사용하여 병렬 작업이 가능함. 여러 future를 조합하여 복잡한 비동기 흐름을 처리할 수 있음. 실행 시간이 오래 걸리는 작업에 대해 Timeout 처리를 추가할 수 있음.

 

동시성패턴 3) PipeLine Pattern

데이터 흐름을 여러 단계(stage)로 처리하여 흐름을 제어하는 방식. 각 단계는 채널을 통해 데이터를 받고 결과를 다음 단계로 전달함. 각 단계는 독립적으로 작동하고 채널로 데이터를 전달하여 비동기적 처리를 지원함. 

도시 ID 리스트를 입력하여 날씨 데이터를 가져옴 → 출력 채널에 전달 → 입력 채널에서 데이터를 받아서 섭씨화씨 변환 후 출력 채널로 전달 → 채널에서 데이터를 받아 출력하거나 저장함.

파이프라인 패턴의 장점

데이터 처리를 여러 단계로 나누어 가독성과 유지보수성 높임. 각 단계가 독립적으로 작동하여 성능 최적화 가능하고 다른 단계로 교체하거나 확장 가능. 데이터 흐름을 정의하고 동시성을 활용해야 하는 때에 적합.

 

동시성패턴 4) Worker pool Pattern

할당된 작업을 처리하려는 고루틴의 집합. 워커 풀의 고루틴의 갯수를 지정하고 고루틴들은 작업에서 나눠진 부작업이 모두 끝날 때까지 종료되지 않고 반복적으로 처리함. (순서는 보장X) 부작업의 수만큼 워커를 생성하는 것이 빠르겠으나 무제한으로 생성하면 과부하가 걸릴 수 있음.

고정된 수의 워커 고루틴이 생성되어 작업 채널을 모니터링 함. 작업이 작업 채널에 추가되면, 대기중인 워커가 이를 처리함. 작업이 완료되면 결과를 결과 채널에 보내거나 저장함. 모든 작업이 완료되면 워커를 종료하고 채널을 닫음. 파이프라인 패턴과 함께 쓰면 좋음.

Dispatcher는 작업 요청(Request)를 받아 작업 채널로 전달함. 워커는 채널에서 요청을 받아 파이프라인 방식으로 순차적으로 작업함. 작업이 완료되면 Handler가 이를 받아 결과를 처리함. 모든 작업이 끝나면 dispatcher를 종료하고 채널을 닫음.

워커풀 패턴의 장점

제한된 워커를 사용하여 시스템 리소스를 효율적으로 활용함. 워커 수를 늘리거나 디스패처를 확장하면 병렬 처리 성능 향상할 수 있음. 작업 생성 / 처리 / 결과 수집을 분리하여 유지보수 용이함.

Class diagram