Go 언어에서 context 없이 Timeout + Interrupt 구현하기

Golang

Go 언어는 context 패키지를 통해 타임아웃과 취소를 간단히 처리할 수 있도록 설계되어 있지만, 꼭 context를 써야만 하는 건 아닙니다. 이번 글에서는 context 없이도 Timeout과 Interrupt 시그널을 처리하는 방법을 알려드리겠습니다.

“아니, context 안 쓰고도 이런 게 가능해?” 라고 생각하셨다면, 네. 가능합니다. 간단한 구조에서는 오히려 context 없이 구현하는 게 더 직관적일 수도 있습니다.


왜 context 없이 처리하려고 할까?

context는 고루틴 취소와 리소스 관리에 최적화되어 있지만, 모든 상황에 맞는 건 아닙니다. 예를 들어:

  • 단일 작업에서만 간단한 타임아웃 또는 인터럽트 처리를 하고 싶을 때
  • context 패턴이 너무 무겁게 느껴질 때
  • 고루틴 안에서 직접적으로 취소 제어가 필요 없을 때

이럴 때는 채널과 select 문만으로도 충분히 타임아웃과 인터럽트를 구현할 수 있습니다.


기본 개념: 타임아웃과 인터럽트를 감지하는 방법

Go에서는 time.After()를 사용하면 일정 시간이 지난 후 자동으로 신호를 보내는 채널을 만들 수 있습니다.

timeout := time.After(5 * time.Second)

또한 os/signal 패키지를 사용하면 운영체제에서 보내는 시그널(Ctrl+C 등)을 감지할 수 있습니다.

sigChan := make(chan os.Signal, 1)signal.Notify(sigChan, os.Interrupt)

이 두 가지를 select문으로 조합하면 context 없이도 작업 제어가 가능합니다.


실전 예제: context 없이 Timeout + Interrupt 처리하기

package main

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

func main() {
    // 인터럽트 시그널 수신 채널
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt)

    // 5초 타임아웃 설정
    timeout := time.After(5 * time.Second)

    // 실제 작업을 시뮬레이션하는 고루틴
    workDone := make(chan struct{})

    go func() {
        // 예: 10초 걸리는 작업
        time.Sleep(10 * time.Second)
        workDone <- struct{}{}
    }()

    fmt.Println("작업을 시작합니다.")

    // 타임아웃, 인터럽트, 작업 완료 중 어떤 것이 먼저 발생하는지 확인
    select {
    case <-workDone:
        fmt.Println("작업이 완료되었습니다.")
    case <-timeout:
        fmt.Println("시간 초과! 작업을 강제 종료합니다.")
    case <-sigChan:
        fmt.Println("사용자 인터럽트 감지! 프로그램을 종료합니다.")
    }
}

실행 결과 시나리오

  • 5초 이내에 Ctrl+C를 누르면 → 사용자 인터럽트 감지!
  • 5초 경과 시 → 시간 초과!
  • 10초 기다리면 → 작업이 완료되었습니다. (단, 타임아웃보다 작업이 빨라야 함)

이 방식의 장점과 단점

✅ 장점

  • 간단한 구조에서 사용하기 쉬움
  • context 패턴을 몰라도 구현 가능
  • 외부 패키지에 의존하지 않음

❌ 단점

  • 작업이 복잡해지면 확장성이 떨어짐
  • 고루틴 간 신호 전달이나 연쇄 취소에 불리함
  • 리소스 정리 등의 정교한 제어는 어려움

실무 팁

  • 간단한 CLI 도구나 단일 프로세스 기반의 유틸리티 프로그램에서는 이 패턴이 유용합니다.
  • 웹서버나 병렬 작업처럼 구조적으로 작업을 전파하거나 중단해야 하는 경우엔 context가 더 적합합니다.
  • os.Kill 시그널은 os/signal로 감지할 수 없으니 주의하세요. 시스템에서 강제 종료할 때는 안전한 종료 처리를 보장할 수 없습니다.

마무리

Go 언어의 강점 중 하나는 단순하고 명확한 병행성 모델입니다. 꼭 context를 써야 하는 것은 아니며, 필요에 따라 selecttime.Afteros/signal 만으로도 충분히 강력한 제어가 가능합니다.

작고 단순한 유틸부터 시작해서, 점차 context 기반의 구조로 확장해 나가는 것도 좋은 학습 전략입니다. 처음엔 작게, 필요할 땐 크게! 이게 Go다운 접근법이 아닐까요?


댓글 달기

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

위로 스크롤