Go

Go로 만드는 데몬 프로세스 처리 레시피

드리프트2 2024. 9. 22. 15:24

Go로 만드는 데몬(daemon) 프로세스 처리 레시피

안녕하세요, 여러분. 오늘은 데몬 프로세스를 만드는 방법에 대해 이야기해볼까 하는데요.

 

데몬(daemon) 프로세스란?

여러분은 어떤 종류의 데몬 프로세스를 작성하고 있나요?

 

여기서 다룰 데몬(daemon)은 백그라운드에서 계속 실행되는 프로세스를 의미하는데요.

 

오타에 주의해야 할 부분입니다. (저는 'deamon'이라고 잘못 쓰는 경우가 많더라고요.)

 

Go 언어는 그 독특한 특성 덕분에 다양한 작업을 간편하게 구현할 수 있는데요.

 

데몬 구현 예제를 통해 Go의 매력을 느낄 수 있을 것이라고 생각합니다.

 

혹시 여러분이 알고 있는 유용한 레시피가 있다면 공유해 주세요.

기본부터 시작해볼까요?

무한 반복 실행하기

아래 코드를 Go Playground에서 시도해보세요.

func main() {
    timeout := 5 * time.Second
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    f := func() error { // 예시 작업
        return nil
    }

    simple(ctx, f)
}

func simple(ctx context.Context, task func() error) {
    for {
        err := task()
        if err != nil {
            log.Printf("[ERROR] err: %v", err)
        }
        time.Sleep(500 * time.Millisecond)
    }
}

컨텍스트를 활용한 타임아웃 설정하기

아래 코드를 Go Playground에서 실행해보세요.

func withTimeout(ctx context.Context, task func() error) {
    child, childCancel := context.WithCancel(ctx)
    defer childCancel()

    for {
        err := task()
        if err != nil {
            log.Printf("[ERROR] err: %v", err)
        }
        select {
        case <-child.Done():
            log.Printf("[DEBUG] timeout")
            return
        default:
            time.Sleep(500 * time.Millisecond)
        }
    }
}

응용편

특정 시간에만 실행하기

아래 코드를 Go Playground에서 실행해보세요.

func runDaemon(ctx context.Context, f func(context.Context) error) {
    daemonHour := "7-10"
    waitDuration := 500 * time.Millisecond

    for {
        now := time.Now()
        if len(daemonHour) != 0 { // 실행 시간 지정이 있을 때
            isExec := isExecHour(now, daemonHour)
            log.Printf("[DEBUG] 현재 시간 체크 now:%v, daemonHour:%s, isExec:%v", now, daemonHour, isExec)
            if !isExec {
                time.Sleep(1 * time.Minute)
                continue
            }
        }

        err := f(ctx)
        if err != nil {
            log.Printf("[ERROR] err:%v", err)
        }
        time.Sleep(waitDuration)
    }
}

func isExecHour(now time.Time, dHour string) bool {
    delimiter := "-"
    dh := strings.Split(dHour, delimiter)
    if len(dh) <= 1 {
        return false
    }

    start, err := strconv.Atoi(dh[0])
    if err != nil {
        return false
    }
    end, err := strconv.Atoi(dh[1])
    if err != nil {
        return false
    }

    h := now.Hour()
    if start <= h && h <= end {
        return true
    }
    return false
}

정기적으로 처리하기 (이전 작업이 끝나지 않아도)

아래 코드를 Go Playground에서 실행해보세요.

func timeTicker(ctx context.Context, task func(context.Context) error) {
    counter := 0
    waitTime := 1 * time.Second
    ticker := time.NewTicker(waitTime)
    defer ticker.Stop()
    child, childCancel := context.WithCancel(ctx)
    defer childCancel()

    for { // 데몬화하기 위한 무한 루프
        select {
        case t := <-ticker.C:
            counter++
            requestID := counter
            log.Println("[DEBUG] 시작 taskNo=", requestID, "t=", t)

            errCh := make(chan error, 1)
            go func() { // 비블록 방식으로 작업 실행
                errCh <- task(ctx)
            }()

            go func() {
                // 에러 채널에서 요청 결과 대기
                select {
                case err := <-errCh:
                    if err != nil {
                        // 데몬 강제 종료
                        log.Println("[ERROR] ", err)
                    }
                    log.Println("[DEBUG] 종료 requestNo=", requestID)
                }
            }()
        case <-child.Done():
            return
        }
    }
}

 

여러분도 멋진 데몬(daemon) 프로세스를 즐기시길 바랍니다!