-
Go 언어의 Goroutine이란?Dev 2025. 3. 2. 19:02728x90
Goroutine은 Go에서 병렬(동시성) 실행을 지원하는 경량 스레드(Lightweight Thread)입니다.
✅ 주요 특징:
- Go 런타임이 관리하는 User-Level Thread (사용자 수준 스레드)
- OS 스레드보다 가벼움 (수천 개의 Goroutine 생성 가능)
- 자동 스케줄링 지원 (M:N 스케줄링)
- go 키워드를 사용하여 생성됨
package main import ( "fmt" "time" ) func sayHello() { fmt.Println("Hello, Goroutine!") } func main() { go sayHello() // Goroutine 생성 time.Sleep(time.Second) // 메인 함수가 종료되지 않도록 대기 }
📌 Goroutine은 User-Level Thread인가?
1. User-Level Thread (ULF)와 Goroutine 비교
특징OS ThreadUser-Level ThreadGoroutine
관리 주체 OS 커널 사용자 공간 (라이브러리) Go 런타임 컨텍스트 스위칭 비용 높음 낮음 매우 낮음 생성 비용 높음 (OS 리소스 필요) 낮음 매우 낮음 스케줄링 방식 OS 커널이 직접 관리 라이브러리가 직접 관리 Go 런타임의 M:N 스케줄링 수천 개 실행 가능? ❌ (OS 제한 있음) ✅ ✅ ✅ Goroutine은 User-Level Thread처럼 동작하지만, OS 스레드에 직접 매핑되지 않음
✅ Go 런타임이 OS 스레드와 Goroutine 간 M:N 스케줄링을 통해 관리
✅ OS 스레드보다 가볍고, 자동으로 조정됨 (Preemptive Scheduling)📌 go 키워드를 사용한 Goroutine 생성
Go에서는 go 키워드를 사용하여 Goroutine을 생성할 수 있음.
🚀 기본적인 Goroutine 예제
package main import ( "fmt" "time" ) func printNumbers() { for i := 1; i <= 5; i++ { fmt.Println(i) time.Sleep(time.Millisecond * 500) // 0.5초 대기 } } func main() { go printNumbers() // Goroutine 실행 fmt.Println("Main function is running") time.Sleep(time.Second * 3) // 메인 함수가 종료되지 않도록 대기 fmt.Println("Main function exits") }
🛠 실행 흐름
- printNumbers()가 Goroutine으로 실행됨.
- 메인 함수는 "Main function is running"을 출력하고 종료되지 않도록 time.Sleep()을 호출.
- Goroutine은 백그라운드에서 실행되다가 메인 함수가 종료되면 같이 종료됨.
중요: go printNumbers()를 호출했지만, 메인 함수가 먼저 종료되면 Goroutine도 종료됨.
📌 Goroutine의 M:N 스케줄링 (Go 런타임)
🚀 Goroutine과 OS 스레드의 관계
Go는 M:N 스케줄링 모델을 사용하여 많은 수의 Goroutine을 적은 수의 OS 스레드에 매핑함.
✅ 핵심 개념
- M개의 Goroutine이 N개의 OS 스레드 위에서 실행됨.
- Go 런타임(Goroutine Scheduler)이 자동으로 OS 스레드를 적절히 할당함.
- 시스템 호출(syscall) 또는 블로킹 연산이 발생하면, 다른 OS 스레드에서 실행되도록 자동 조정됨.
[Goroutine 1] ────> [OS Thread 1][Goroutine 2] ────> [OS Thread 2][Goroutine 3] ────> [OS Thread 1] (스위칭)[Goroutine 4] ────> [OS Thread 2]Go 런타임이 Goroutine을 효율적으로 OS 스레드에 배치하여 실행 성능을 최적화함.
📌 Goroutine이 멈추지 않게 하려면?
메인 함수가 종료되면 Goroutine도 함께 종료됨. 이를 방지하려면 sync.WaitGroup 또는 **채널(channel)**을 사용해야 함.
1. sync.WaitGroup을 사용한 Goroutine 관리
package main import ( "fmt" "sync" ) func printMessage(wg *sync.WaitGroup) { defer wg.Done() // Goroutine 종료 시 `Done()` 호출 fmt.Println("Hello from Goroutine!") } func main() { var wg sync.WaitGroup wg.Add(1) // Goroutine 개수 추가 go printMessage(&wg) // Goroutine 실행 wg.Wait() // 모든 Goroutine이 끝날 때까지 대기 fmt.Println("Main function exits") }
✅ 설명
- wg.Add(1) → Goroutine 추가.
- wg.Done() → Goroutine이 끝나면 Done() 호출.
- wg.Wait() → 모든 Goroutine이 끝날 때까지 대기.
2. 채널을 사용한 Goroutine 동기화
package main import ( "fmt" ) func worker(done chan bool) { fmt.Println("Worker started") done <- true // 채널에 값 전송 (완료 알림) } func main() { done := make(chan bool) go worker(done) // Goroutine 실행 <-done // 채널에서 값이 올 때까지 대기 fmt.Println("Worker finished") }
✅ 설명
- done 채널을 통해 Goroutine이 끝났음을 알림.
- <-done으로 대기하면서 Goroutine이 종료될 때까지 기다림.
📌 Goroutine vs OS 스레드 성능 비교
package main import ( "fmt" "runtime" "time" ) func main() { fmt.Println("CPU Cores:", runtime.NumCPU()) // 사용 가능한 CPU 코어 수 fmt.Println("Goroutines before:", runtime.NumGoroutine()) // 현재 실행 중인 Goroutine 개수 for i := 0; i < 1000; i++ { go func() { time.Sleep(time.Second) }() } fmt.Println("Goroutines after:", runtime.NumGoroutine()) // 1000개 이상의 Goroutine 실행 time.Sleep(time.Second * 2) // Goroutine이 실행될 시간을 줌 }
✅ 실행 결과
CPU Cores: 8
Goroutines before: 1
Goroutines after: 1001- Goroutine은 수천 개 이상 실행 가능 → OS 스레드보다 가벼움!
- OS 스레드는 보통 몇 백 개 이상 실행하면 성능이 크게 저하됨.
🚀 결론
✅ Goroutine은 Go 런타임이 관리하는 User-Level Thread
- OS 스레드보다 가벼움.
- go 키워드를 사용하여 생성.
✅ M:N 스케줄링을 사용하여 최적화
- 여러 개의 Goroutine이 적은 수의 OS 스레드에서 실행됨.
- Go 런타임이 자동으로 최적화.
✅ Goroutine이 멈추지 않게 하려면?
- sync.WaitGroup 또는 채널(channel) 사용.
✅ OS 스레드 대비 장점
- 수천 개의 Goroutine 실행 가능.
- 컨텍스트 스위칭 비용이 낮음.
📌 즉, Goroutine은 OS 스레드보다 가볍고 Go의 동시성(Concurrency)을 강력하게 지원하는 핵심 기능! 🚀
728x90'Dev' 카테고리의 다른 글
Go 언어 기원 및 설계 철학 (0) 2025.03.20 Go에서 함수와 상수 이름의 시작 규칙 (대문자 vs. 소문자) (0) 2025.03.03 go언어에서 Java의 클래스, 속성, 메소드는 어떻게 구현이되? (1) 2025.03.02 Django에서 admin user 생성하기 위한 명령 (1) 2024.09.18 yarn 주요 명령어 (0) 2024.08.16