Go 언어에서 Cancel Context with Interrupt과 Multi Jobs 처리하기

Golang

Go 언어를 사용하면서 가장 많이 접하게 되는 고민 중 하나는 바로 여러 작업을 동시에 처리하면서, **필요할 때 안전하게 중단(cancel)**하는 것입니다. 특히 사용자 인터럽트(Ctrl+C)나 타임아웃 등으로 인해 여러 goroutine을 정리해야 하는 경우, 단순한 로직만으로는 복잡도가 급격히 올라갑니다.

이럴 때 context 패키지를 잘 활용하면 고루틴 수십 개도 한 번의 cancel로 깔끔하게 멈추게 할 수 있습니다.

이번 글에서는 Go의 context.WithCancel과 os.Interrupt를 조합해 여러 작업을 동시에 실행하고, 안전하게 중단하는 실전 패턴을 알려드리겠습니다.


왜 Cancel Context가 필요한가?

고루틴은 기본적으로 병렬로 독립 실행되기 때문에, 외부에서 중단 신호를 보내지 않는 이상 계속 실행됩니다. 이 말은 곧, 여러 개의 고루틴을 띄워놓고 **중간에 취소(cancel)**하고 싶다면, 각 고루틴에 신호를 전달할 **공통 수단(context)**이 필요하다는 뜻입니다.


핵심 기술: context + os.Interrupt + waitGroup

이번 예제에서는 다음 기술을 사용합니다:

기술설명
context.WithCancel수동으로 cancel 신호 전달
os/signal.NotifyCtrl+C 등 인터럽트 감지
sync.WaitGroup모든 고루틴 종료 대기

실전 예제: 여러 작업을 실행하고 Cancel Context로 안전하게 멈추기

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "sync"
    "time"
)

func main() {
    // Cancel 가능한 context 생성
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 인터럽트 감지 채널
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt)

    // Ctrl+C 입력 시 cancel() 호출
    go func() {
        <-sigChan
        fmt.Println("\n인터럽트 시그널 수신! 모든 작업을 취소합니다.")
        cancel()
    }()

    var wg sync.WaitGroup
    jobCount := 5

    fmt.Println("여러 작업을 동시에 실행합니다.")

    for i := 1; i <= jobCount; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            runJob(ctx, id)
        }(i)
    }

    // 모든 작업이 끝날 때까지 대기
    wg.Wait()
    fmt.Println("모든 작업이 종료되었습니다.")
}

func runJob(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("작업 #%d 종료됨 (사유: %v)\n", id, ctx.Err())
            return
        default:
            fmt.Printf("작업 #%d 진행 중...\n", id)
            time.Sleep(1 * time.Second)
        }
    }
}

실행 결과 예시

  • 프로그램을 실행하면 작업 #1 진행 중...작업 #2 진행 중... 등의 메시지가 5개 고루틴에서 동시에 출력됩니다.
  • 이 상태에서 Ctrl+C를 누르면, 인터럽트가 감지되고 cancel()이 호출되며 모든 고루틴이 순차적으로 종료됩니다.
  • 마지막으로 "모든 작업이 종료되었습니다." 메시지가 출력됩니다.

확장 가능한 구조

이 패턴은 다음과 같이 확장 가능합니다:

  • context.WithTimeout() 또는 WithDeadline()을 써서 일정 시간 후 자동 중단
  • select 내부에 네트워크 요청, 파일 처리 등 실질 작업 삽입
  • runJob() 함수 안에서 필요한 리소스 정리 로직 추가 (예: defer conn.Close())

실무에서의 활용 예

  • 웹 크롤러: 수십 개의 URL을 병렬로 크롤링하다가 중간에 중단
  • 서버 작업자(worker pool): 작업 큐 처리 중 서버 종료 신호 감지
  • 데이터 마이그레이션 도구: 복수의 테이블을 동시에 이전하다가 취소 조건 발생 시 중단

마무리

Go의 context.WithCancel은 단순한 함수 취소를 넘어서, 여러 고루틴을 동시에 제어하고, 안전하게 종료할 수 있는 매우 강력한 도구입니다. 여기에 os/signal을 결합하면 사용자 인터럽트에 대응할 수 있고, sync.WaitGroup을 추가하면 전체 작업이 끝날 때까지 기다릴 수 있습니다.

작업이 많아질수록 복잡도는 올라가지만, 이 패턴만 잘 익혀두면 언제든 안정적이고 유연하게 작업을 제어할 수 있습니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤