Go 언어의 고루틴 (Goroutine)
- 고루틴은 Go 언어에서 제공하는 경량 스레드로, 병렬 처리를 쉽게 구현할 수 있는 기능
- 고루틴은 운영 체제의 스레드보다 훨씬 가볍고, 수천 개의 고루틴을 동시에 실행할 수 있음
- 채널(channel)과 함께 사용하여 고루틴 간의 통신과 동기화를 처리할 수 있음
고루틴의 특징
1. 경량성
- 고루틴은 운영 체제의 스레드와는 다르게 더 적은 리소스를 사용
- 하나의 Go 프로그램에서 수천 개 이상의 고루틴을 생성할 수 있음
2. 자동 관리
- Go 런타임이 고루틴의 실행과 스케줄링을 관리하므로 개발자가 직접 스레드 관리를 하지 않아도 됨
3. 간단한 구문
- go 키워드만 사용하면 함수를 고루틴으로 실행할 수 있음
4. 스택 크기
- 고루틴의 초기 스택 크기는 매우 작으며(약 2kb), 필요에 따라 자동으로 확장
고루틴 사용법
고루틴 기본 문법
go functionName(arguments)
- 함수 호출 앞에 go 키워드를 붙이면 해당 함수는 별도의 고루틴으로 실행
고루틴 예제
1. 간단한 고루틴
package main
import (
"fmt"
"time"
)
func printMessage(message string) {
for i := 0; i < 5; i++ {
fmt.Println(message)
time.Sleep(500 * time.Millisecond) // 0.5초 대기
}
}
func main() {
// 고루틴 실행
go printMessage("Hello from Goroutine")
// 메인 함수에서도 동작
for i := 0; i < 5; i++ {
fmt.Println("Hello from Main")
time.Sleep(500 * time.Millisecond) // 0.5초 대기
}
// 고루틴이 끝나기 전에 메인 함수 종료 방지
time.Sleep(3 * time.Second)
}
출력 예시
Hello from Main
Hello from Goroutine
Hello from Main
Hello from Goroutine
...
2. 고루틴과 채널을 사용한 통신
- 고루틴은 보통 채널(channel)을 사용하여 데이터를 주고 받음
package main
import (
"fmt"
)
func sum(a int, b int, result chan int) {
result <- a + b // 채널에 결과 전달
}
func main() {
// 채널 생성
result := make(chan int)
// 고루틴 실행
go sum(3, 5, result)
// 채널로부터 결과 수신
total := <-result
fmt.Println("Sum:", total)
}
출력 결과
Sum: 8
3. 여러 고루틴 실행
- 여러 고루틴을 실행하고 채널로 결과를 수집하는 예제
package main
import (
"fmt"
"time"
)
func worker(id int, result chan string) {
fmt.Printf("Worker %d started\n", id)
time.Sleep(1 * time.Second) // 작업 시뮬레이션
result <- fmt.Sprintf("Worker %d done", id)
}
func main() {
result := make(chan string, 3) // 버퍼 크기 3
for i := 1; i <= 3; i++ {
go worker(i, result)
}
for i := 1; i <= 3; i++ {
fmt.Println(<-result)
}
}
출력 결과
Worker 1 started
Worker 2 started
Worker 3 started
Worker 1 done
Worker 2 done
Worker 3 done
4. 고루틴 동기화
- 고루틴 간 동기화는 채널이나 sync패키지를 사용
1. WaitGroup을 활용한 동기화
sync.WaitGroup을 사용하면 고루틴의 종료를 기다릴 수 있음
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 고루틴 작업 완료 알림
fmt.Printf("Worker %d started\n", id)
time.Sleep(1 * time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 고루틴 추가
go worker(i, &wg)
}
wg.Wait() // 모든 고루틴 완료 대기
fmt.Println("All workers done")
}
출력 결과
Worker 1 started
Worker 2 started
Worker 3 started
Worker 1 done
Worker 2 done
Worker 3 done
All workers done
5. 다른 언어와의 차이점
1. 운영 체제 스레드와의 분리
- Go의 고루틴은 운영체제의 스레드와 직접 매핑되지 않음. Go 런타임이 스케줄러를 통해 효율적으로 스레드 풀에서 고루틴을 관리
- Java, Python 등에서는 스레드가 운영 체제 수준에서 직접 관리
2. 경량성
- Java나 C++에서 스레드는 상대적으로 많은 리소스를 소비
- 고루틴은 메모리와 CPU 사용량이 훨씬 적음
3. 간단한 문법
- Go에서는 go키워드만으로 고루틴을 실행할 수 있음
- 다른 언어에서는 스레드 생성과 실행에 복잡한 API 호출이 필요함
6. 고루틴의 한계와 주의점
1. 공유 메모리 문제
- 고루틴은 같은 메모리를 공유하므로 동기화 문제가 발생할 수 있음. sync.Mutex를 사용해 동기화를 처리
2. 잘못된 종료
- 고루틴이 끝나기 전에 메인 함수가 종료되면, 고루틴의 실행이 중단될 수 있음. 이를 방지하려면 채널이나 sync.WaitGroup을 사용
3. 과도한 생성
- 고루틴은 가볍지만, 너무 많이 생성하면 런타임 스케줄링에 부하가 걸릴 수 있음.
결론
고루틴은 Go 언어의 핵심 기능으로, 병렬 처리와 동시성 프로그래밍을 간단하고 효율적으로 구현할 수 있다.
실무에서는 고루틴과 채널을 함께 사용해 안정적이고 동기화된 프로그램을 개발하는 것이 중요함.
'프로그래밍 > Go' 카테고리의 다른 글
[Go] 채널 Channel 심화 (1) | 2024.11.23 |
---|---|
[Go] 채널 Channel 기초 (0) | 2024.11.23 |
[Go] 인터페이스 interface (0) | 2024.11.23 |
[Go] 구조체 (Struct) (0) | 2024.11.22 |
[Go] Closure (0) | 2024.11.21 |