[TIL] Go 동시성

  • Go는 goroutine이라는 일종의 경량 스레드를 제공한다.
  • goroutine을 관리하는 Go Scheduler가 있다.
  • Go Scheduler는 m:n 스케줄링 기법을 사용한다. 즉, m개의 goroutine을 n개의 OS 스레드로 멀티플렉싱(multiplexing)한다.
  • Go Scheduler는 런타임 구성요소다.
  • 병렬성은 “동시에 실행되는 것"을 의미한다.
  • 동시성은 각 컴포넌트들이 최대한 독립적으로 실행될 수 있게 구성하는 것을 의미한다.
  • 동시성을 지원하도록 소프트웨어를 작성해야 안전하게 병렬로 실행할 수 있다.
  • goroutine은 백그라운드에서 실행된다.
func some_func() {
    fmt.Println("Func!")
}

main() {
    go some_func()
}
  • goroutine은 특별히 신경쓰지 않으면 실행 순서를 제어할 수 없다. 즉, 각각의 goroutine이 순차적으로 실행됨을 보장하지 않는다.
func main() {
    var waitGroup sync.WaitGroup

    for i := range 10 {
        waitGroup.Add(1)
        go func() {
            defer waitGroup.Done()
            fmt.Println("go")
        }()
    }
}
  • sync.WaitGroup을 이용하면 모든 goroutine이 끝날 때까지 main함수가 종료되지 않게 할 수 있다.
  • waitGroup.Add()waitGroup.Done()은 동일한 호출횟수를 가져야 panic이나 fatal error가 발생하지 않는다.
  • waitGroup.Add()waitGroup.Done()보다 많이 호출되면 Done의 호출을 계속 기다리게 되는 데드락이 발생한다. (fatal error)
  • waitGroup.Done()이 더 많이 호출되면 panic이 발생한다.
  • 채널을 통해 goroutine끼리 데이터를 주고받을 수 있다.
  • 각 채널마다 특정한 데이터 타입으로만 값을 교환할 수 있다.
func write(c chan int) {
    c <- 10 // 10을 c에 기록
    close(c)
}

func main() {
    c := make(chan int)
    go write(c)
    fmt.Println("Read: " <- c)
    // ... sleep
}
  • 값을 채널에서 읽으면 채널은 닫힌다.
func f1(out <-chan int) {} // 읽기 전용 채널
func f2(in chan<- int) {} // 쓰기 전용 채널
  • 파이프라인은 goroutine과 채널을 연결하는 일종의 가상 메서드로 한쪽 goroutine의 출력을 다른쪽 goroutine의 입력으로 넘겨줄 수 있다.
func f1(out chan<- int) {
    out <- 10
    // close(out)
}
func f2(out chan<- int, in <-chan int) {
    out <- in
    // close(out)
}

func f3(in <-chan int) {
    // ...
}

main() {
    A := make(chan int)
    B := make(chan int)

    go f1(A)
    go f2(B, A)
    f3(B)
}
  • 추가적으로 select, 시그널채널 등 보다 깊이 공부해야할 부분들이 있다.