Go의 동시성 모델은 경량 스레드인 goroutine과 채널(channel)을 통한 통신을 기반으로 한다. "공유 메모리로 통신하지 말고, 통신으로 메모리를 공유하라"는 철학으로 설계되었다.
goroutine
go
package main
import (
"fmt"
"sync"
"time"
)
// goroutine: go 키워드로 비동기 실행
func printNumbers(wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
fmt.Println(i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go printNumbers(&wg) // goroutine 시작
go printNumbers(&wg) // 또 다른 goroutine
wg.Wait() // 모두 완료될 때까지 대기
}
Channel (통신)
go
// 방향 있는 채널
func producer(ch chan<- int) { // 전송 전용
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
func consumer(ch <-chan int) { // 수신 전용
for v := range ch {
fmt.Println(v)
}
}
func main() {
ch := make(chan int, 5) // 버퍼 채널
go producer(ch)
consumer(ch)
}
select 문 (멀티플렉싱)
go
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
timeout := time.After(2 * time.Second)
go func() { time.Sleep(1 * time.Second); ch1 <- "one" }()
go func() { time.Sleep(500 * time.Millisecond); ch2 <- "two" }()
for i := 0; i < 2; i++ {
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
case <-timeout:
fmt.Println("타임아웃!")
return
}
}
}
워커 풀 패턴
go
func workerPool(jobs <-chan int, results chan<- int, workerCount int) {
var wg sync.WaitGroup
for w := 0; w < workerCount; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- job * job // CPU 집약적 작업
}
}()
}
go func() {
wg.Wait()
close(results)
}()
}
goroutine vs 스레드 비교
| 항목 | OS 스레드 | goroutine |
|---|
| 스택 크기 | ~1MB 고정 | ~2KB (동적 확장) |
| 생성 비용 | 높음 | 매우 낮음 |
| 컨텍스트 전환 | OS 관리 | Go 런타임 관리 |
| 동시 실행 수 | 수천 | 수십만~수백만 |
관련 개념