Go에서의 Concurrency 파트 1: 기본 및 동기화
Source: Dev.to
Concurrency와 Synchronization 기본
먼저 이 두 용어를 이해해봅시다.
Concurrency
정의는 동시에 여러 작업을 관리할 수 있는 능력이다.
각 작업이 반드시 동시에 실행될 필요는 없으며, 작업을 번갈아 수행하는 것만으로도 concurrency라고 할 수 있다.
핵심 특징은 각 작업의 시작 시점이 overlap(겹치는) 것이다.
Parallelism
정의는 하드웨어를 이용해 여러 작업을 동시에 처리할 수 있는 능력이다.
※ 참고: concurrency는 parallelism일 수도, 아닐 수도 있으며, 하드웨어와 매핑되는 방식에 따라 다릅니다.
비록 병렬로 실행되지 않더라도 concurrent하게 실행하면 작업 속도가 빨라질 수 있다. 일반적인 작업은 메모리에서 값을 읽거나 쓰는 등 대기 시간이 필요하기 때문에, 대기 중에 다른 작업을 전환하면 전체 효율이 향상됩니다.
Go에서의 Concurrency
Go는 외부 라이브러리 없이 내장된 concurrency를 제공합니다.
GOMAXPROCS
동시에 Go 코드를 실행할 수 있는 OS 스레드 수를 지정합니다.
runtime.GOMAXPROCS(4) // 코드에서 설정
또는 환경 변수로 설정합니다.
export GOMAXPROCS=4
Go 1.5부터 GOMAXPROCS의 기본값은 머신의 논리 CPU 수와 동일하므로 별도로 설정할 필요가 없습니다. 값을 너무 높게 설정하면 컨텍스트 스위칭이 증가해 성능이 저하될 수 있습니다.
Goroutine
Go는 goroutine을 사용합니다. 이는 경량 스레드이며(하나의 OS 스레드 안에 수천~수만 개가 존재할 수 있음) Go 런타임 스케줄러가 작업을 스케줄링합니다.
프로그램이 시작될 때 main()에서 Go는 main routine이라 불리는 첫 번째 goroutine을 생성합니다. concurrent하게 실행하려면 함수 호출 앞에 go 키워드를 붙이면 됩니다.
기본 예제
package main
import (
"fmt"
"time"
)
func main() {
go fmt.Println("New Routine")
fmt.Println("Main Routine")
}
출력은 다음과 같이 보일 것입니다.
Main Routine
main()이 새 goroutine이 실행되기 전에 종료되기 때문입니다.
goroutine을 올바르게 대기하는 방법
time.Sleep 사용은 권장되지 않습니다. 대기 시간이 불확실하기 때문입니다. 대신 sync.WaitGroup과 같은 동기화 메커니즘을 사용하는 것이 적절합니다.
sync 패키지를 이용한 Synchronization
sync.WaitGroup
WaitGroup은 기다려야 할 작업 수를 카운트합니다.
Add(n)– 카운트를 증가시킴Done()– 작업이 끝났을 때 카운트를 감소시킴Wait()– 카운트가 0이 될 때까지 대기
예제
package main
import (
"fmt"
"sync"
)
func foo(wg *sync.WaitGroup) {
fmt.Println("New Routine")
wg.Done() // 작업이 끝났음을 알림
}
func main() {
var wg sync.WaitGroup
wg.Add(2) // 기다릴 작업이 2개
go foo(&wg)
go foo(&wg)
wg.Wait() // 모든 작업이 끝날 때까지 대기
fmt.Println("Main Routine")
}
출력
New Routine
New Routine
Main Routine
sync.Once
Once는 지정된 함수가 여러 goroutine에서 호출되더라도 한 번만 실행되도록 보장합니다.
예제
package main
import (
"fmt"
"sync"
)
func setup() {
fmt.Println("Setup")
}
func main() {
var once sync.Once
var wg sync.WaitGroup
for i := 0; i // (코드 예제가 원본에서 완전하지 않음)
}
※ 참고: Go에서는 중앙 변수를 공유하고 직접 값을 수정하는 것을 피하고, 대신 channel을 사용합니다. 이는 다음 글에서 설명합니다.