
Go 언어는 병렬 처리를 간단하고 강력하게 구현할 수 있도록 goroutine과 채널이라는 도구를 제공합니다. 일반적으로 여러 작업을 동시에 처리하고 중간에 중단하거나 타임아웃을 주고 싶을 때는 context
를 사용하는 것이 권장되지만, 모든 상황에서 꼭 필요한 것은 아닙니다.
이번 글에서는 context
없이도 goroutine과 채널만으로 여러 개의 작업(Multi Job)을 효율적으로 실행하고 제어하는 방법을 소개하겠습니다. 특히 가볍고 단순한 구조를 원하는 경우에 매우 유용한 패턴입니다.
왜 context 없이 처리하려 할까?
Go의 context
는 취소 신호 전파, 타임아웃 관리 등에 유용하지만, 다음과 같은 경우에는 오히려 불필요하게 느껴질 수 있습니다:
- 프로그램 구조가 단순하고 복잡한 취소 체계가 필요하지 않은 경우
- 고루틴 종료를
WaitGroup
과 채널로 충분히 제어할 수 있는 경우 - 외부 패키지나 함수가
context
를 요구하지 않는 경우
이럴 땐 오히려 context
없이 코드를 구성하는 것이 더 깔끔하고 직관적입니다.
핵심 구성 요소
구성 요소 | 역할 |
---|---|
goroutine | 각 작업을 병렬로 실행 |
sync.WaitGroup | 모든 작업이 끝날 때까지 기다림 |
chan struct{} | (선택) 종료 또는 신호 전달용 |
실전 예제: context 없이 5개의 작업 동시에 처리하기
package main
import (
"fmt"
"sync"
"time"
)
func main() {
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(id)
}(i)
}
// 모든 작업이 끝날 때까지 대기
wg.Wait()
fmt.Println("모든 작업이 완료되었습니다.")
}
func runJob(id int) {
fmt.Printf("작업 #%d 시작\n", id)
for i := 0; i < 3; i++ {
fmt.Printf("작업 #%d 진행 중... (%d/3)\n", id, i+1)
time.Sleep(1 * time.Second)
}
fmt.Printf("작업 #%d 완료\n", id)
}
결과 예시
여러 작업을 동시에 실행합니다.
작업 #1 시작
작업 #2 시작
작업 #3 시작
작업 #4 시작
작업 #5 시작
작업 #3 진행 중... (1/3)
작업 #1 진행 중... (1/3)
작업 #4 진행 중... (1/3)
...
모든 작업이 완료되었습니다.
중간에 취소하고 싶다면? (간단한 종료 시그널 추가)
context
없이도 고루틴에 종료 신호를 보내고 싶다면 공통 채널을 닫는 방식으로 구현할 수 있습니다
stop := make(chan struct{})
각 goroutine 내부에서:
select {
case <-stop:
fmt.Printf("작업 #%d 중단됨\n", id)
return
default:
// 계속 진행
}
그리고 특정 조건(예: 오류 발생)에서 close(stop)
을 호출하면 모든 고루틴이 이를 감지하고 종료할 수 있습니다.
장점과 단점
✅ 장점
context
없이도 goroutine을 안전하게 병렬 실행- 간단한 구조에서 직관적인 코드
- 외부 종속성 없이 표준 패키지만으로 구현 가능
❌ 단점
- goroutine이 많아질수록 종료 제어가 복잡해짐
- 취소 전파와 타임아웃 같은 고급 기능은 직접 구현해야 함
- 외부 API 호출, 데이터베이스 트랜잭션에는 적합하지 않음
실무 팁
context
를 도입하기 전에 먼저 이 구조로 작은 프로그램을 설계해보세요.- 단일 책임 goroutine을 만든 후, 채널을 통해 입력/출력을 제어하면 더 유연한 구조가 됩니다.
- 클라이언트가 취소 요청을 보낼 수 없는 환경 (예: 로컬 스크립트 실행)에서는 굳이 context를 쓰지 않아도 됩니다.
마무리
Go 언어는 병렬 처리에 강력한 도구들을 제공합니다. 꼭 context
를 써야만 여러 작업을 제어할 수 있는 건 아닙니다. 때로는 단순한 goroutine + WaitGroup
조합이 가장 명확하고 효율적인 선택이 될 수 있습니다.
작고 단순한 프로그램일수록, 꼭 필요한 기능만 구현하고 의존성을 줄이는 것이 코드 유지 관리에 큰 도움이 됩니다.