
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
를 써야 하는 것은 아니며, 필요에 따라 select
, time.After
, os/signal
만으로도 충분히 강력한 제어가 가능합니다.
작고 단순한 유틸부터 시작해서, 점차 context
기반의 구조로 확장해 나가는 것도 좋은 학습 전략입니다. 처음엔 작게, 필요할 땐 크게! 이게 Go다운 접근법이 아닐까요?