defer까지 이해했다면, 이제 Go를 선택하는 가장 큰 이유 중 하나인
동시성(concurrency) 을 다룰 차례다.
Go의 동시성은 복잡한 스레드 제어 대신
goroutine과 channel이라는 비교적 단순한 개념을 중심으로 설계되어 있다.
문법은 간단하지만, 개념을 제대로 이해하지 않으면 예상치 못한 동작을 만들기 쉽다.
이 글에서는
- goroutine이 무엇인지
- 어떻게 실행되고 관리되는지
- 실제로 써보면서 느끼는 특징
을 중심으로 정리한다.
goroutine이란 무엇인가
goroutine은 Go에서 제공하는 경량 스레드(lightweight thread) 다.
go doWork()
- go 키워드를 붙이면 새로운 goroutine에서 함수가 실행된다
- 함수 호출 자체는 즉시 반환된다
이 한 줄로 동시 실행이 가능해진다는 점이
Go 동시성 모델의 가장 큰 특징이다.
[이미지: goroutine 실행 개념 구조]
goroutine은 얼마나 가벼운가
운영체제 스레드와 비교했을 때 goroutine은 훨씬 가볍다.
- 초기 스택 크기가 작다
- 필요에 따라 스택이 자동으로 확장된다
- 수천~수만 개도 현실적으로 생성 가능하다
실제로 써보면,
“이걸 스레드로 만들어도 되나?” 고민하던 지점에서
goroutine을 부담 없이 사용하게 된다.
goroutine의 실행 시점은 보장되지 않는다
go fmt.Println("hello")
fmt.Println("world")
출력 순서는 보장되지 않는다.
- goroutine은 스케줄러에 의해 실행
- 언제 실행될지는 알 수 없다
⚠️ 주의할 점
goroutine 실행 순서나 완료 시점을 가정하고 코드를 작성하면
불안정한 코드가 된다.
이 때문에 goroutine은
혼자서는 거의 의미가 없고,
반드시 동기화 수단과 함께 사용된다.
main 함수와 goroutine의 관계
func main() {
go doWork()
}
이 코드는 대부분의 경우 아무 일도 하지 않고 종료된다.
- main 함수가 종료되면 프로그램도 종료
- 실행 중인 goroutine이 있어도 기다려주지 않는다
그래서 goroutine을 사용할 때는
작업 완료를 기다리는 구조가 반드시 필요하다.
[이미지: main 종료 시 goroutine 종료 흐름]
가장 단순한 동기화: time.Sleep
초기 학습 단계에서 흔히 보는 예제다.
go doWork()
time.Sleep(time.Second)
- goroutine이 실행될 시간을 벌어준다
- 학습용으로는 이해하기 쉽다
하지만 실무에서는 거의 사용하지 않는다.
⚠️ 주의할 점
- 실행 환경에 따라 충분하지 않을 수 있다
- 정확한 동기화 수단이 아니다
이 한계를 해결하기 위해
Go는 channel과 sync 패키지를 제공한다.
여러 goroutine 실행 예제
for i := 0; i < 3; i++ {
go func(n int) {
fmt.Println(n)
}(i)
}
- 반복문에서 goroutine을 만들 때는
루프 변수 캡처에 주의해야 한다 - 위 예제처럼 인자를 명시적으로 넘기는 게 안전하다
이 부분은 실제로 실수하기 가장 쉬운 포인트 중 하나다.
[이미지: goroutine과 반복문 변수 캡처 문제]
goroutine 설계 시 기본적인 기준
실무에서 goroutine을 사용할 때는
대략 다음 기준을 두는 편이다.
- 독립적인 작업 단위인가?
- 실행 순서에 의존하지 않는가?
- 실패 시 전체 흐름에 어떤 영향을 주는가?
goroutine을 많이 쓴다고 해서
코드가 자동으로 빨라지거나 좋아지지는 않는다.
오히려 제어하지 않으면 복잡도가 급격히 올라간다.
정리
goroutine은
동시 실행을 쉽게 만들지만, 제어는 개발자의 책임이다.
- go 키워드 하나로 실행 가능
- 실행 시점과 순서는 보장되지 않는다
- 반드시 동기화 수단과 함께 사용해야 한다
goroutine의 개념을 정확히 이해하고 나면,
다음 단계로 channel을 통한 통신과 동기화를 배우는 게 자연스럽다.
다음 글에서는 goroutine과 항상 함께 등장하는
channel의 기본 개념과 사용 패턴을 다뤄보자.
'backend' 카테고리의 다른 글
| Go 언어 select 문 정리: 여러 channel을 동시에 다루는 방법 (0) | 2026.01.18 |
|---|---|
| Go 언어 channel 기초: goroutine 간 통신과 동기화 방법 (1) | 2026.01.17 |
| Go 언어 에러 처리 방식 정리: error 인터페이스와 실전 패턴 (0) | 2026.01.15 |
| Go 언어 defer 정리: 자원 관리와 실행 순서 이해하기 (0) | 2026.01.14 |
| Go 언어 인터페이스(interface): 느슨한 결합과 다형성 이해하기 (0) | 2026.01.13 |