Go 언어에서 Cancel Context with Interrupt 없이 구현하기

Golang

Go 언어는 context.WithCancel이나 context.WithTimeout을 통해 손쉽게 작업 취소와 타임아웃 처리를 할 수 있는 강력한 기능을 제공합니다. 하지만, 간단한 프로그램이나 초경량 CLI 도구를 개발할 때는 오히려 context를 사용하는 것이 과도한 설계처럼 느껴질 수도 있습니다.

이번 글에서는 context를 사용하지 않고도 어떻게 Cancel + Timeout + Interrupt(인터럽트 시그널 처리)를 구현할 수 있는지, 실제 코드 예제와 함께 자세히 설명드리겠습니다.


Cancel Context with Interrupt란?

context.WithCancel()과 os/signal.Notify()를 조합하면, 프로그램이 Ctrl+C 같은 운영체제의 인터럽트 시그널을 받을 때 자동으로 작업을 중단(cancel)하도록 구성할 수 있습니다.

하지만, 여기서는 이 편리한 context 없이도 똑같은 기능을 구현하는 방식에 집중합니다.


핵심 원리: select + 채널

Go에서는 select 문을 활용해 여러 채널의 이벤트를 동시에 감시할 수 있습니다. 이를 이용해 다음 세 가지 상황을 감지합니다:

  • 작업이 완료되었을 때
  • 타임아웃이 발생했을 때
  • 사용자가 Ctrl+C를 눌렀을 때

실제 예제: context 없이 Cancel Context with Interrupt 구현하기

package main

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

func main() {
    // 사용자 인터럽트(Ctrl+C)를 수신할 채널
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt)

    // 타임아웃 설정 (예: 10초 후 자동 취소)
    timeout := time.After(10 * time.Second)

    // 작업 완료 여부를 확인할 채널
    done := make(chan struct{})

    // 실제 작업 실행
    go func() {
        fmt.Println("작업을 시작합니다...")
        // 예시: 30초 걸리는 작업 (하지만 타임아웃 or 인터럽트가 먼저 올 수 있음)
        time.Sleep(30 * time.Second)
        done <- struct{}{}
    }()

    // 이벤트 감시
    select {
    case <-done:
        fmt.Println("작업이 정상적으로 완료되었습니다.")
    case <-timeout:
        fmt.Println("타임아웃 발생! 작업을 강제 종료합니다.")
    case <-sigChan:
        fmt.Println("사용자 인터럽트 감지! 작업을 중단합니다.")
    }
}

실행 결과

  • 10초 이내에 Ctrl+C를 누르면 사용자 인터럽트 감지! 메시지 출력
  • 10초 경과 시 자동으로 타임아웃 발생! 출력
  • 30초 기다리면 작업이 정상적으로 완료되었습니다. 출력 (단, 인터럽트나 타임아웃이 먼저 발생하지 않은 경우)

장점과 단점

✅ 장점

  • context에 대한 의존이 없어서 더 간결하고 가벼운 코드
  • Go 채널과 select 문만으로도 직관적인 흐름 제어 가능
  • 간단한 CLI 도구, 테스트 스크립트, 초기 프로토타입에 적합

❌ 단점

  • 여러 goroutine 간 취소 신호 전파가 어려움
  • 복잡한 로직에는 확장성이 떨어짐
  • 외부 라이브러리(API, DB 등)는 context를 요구하는 경우가 많음

응용 팁

이 방식은 다음과 같은 상황에 유용합니다:

  • 간단한 파일 처리, 파싱 스크립트, 로컬 유틸리티
  • 무거운 구조 없이 인터럽트 처리를 해야 할 때
  • 리소스 정리가 단순하거나 필요 없는 경우

하지만, 웹 서버, 데이터베이스 연결, API 요청과 같이 중첩된 취소가 필요한 구조에서는 반드시 context 사용을 고려해야 합니다.


마무리

Go의 context는 분명히 강력한 도구지만, 모든 상황에 필수는 아닙니다. 오히려 단순한 프로그램에서는 selecttime.After()os/signal 만으로도 충분히 취소와 타임아웃을 제어할 수 있습니다.

개발의 핵심은 “지금 필요한 만큼만 설계하자”는 것입니다. 필요한 순간에 context를 도입하고, 그 전까지는 채널만으로도 충분히 깔끔한 로직을 만들 수 있다는 점, 꼭 기억해두시길 바랍니다.

댓글 달기

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

위로 스크롤