18.2. 프로그램언어 고(Go)의 고루틴 동기화

프로그램언어 고(Go)에서의 뮤텍스 Mutex 활용

고(Go)언어에서 뮤텍스(Mutex)는 고루틴(goroutine)간에 안전하게 데이터를 공유하기 위해 사용합니다.

뮤텍스는 lock과 unlock 메서드를 사용하여 크리티컬 섹션(critical section)을 보호합니다. 한 고루틴이 lock을 획득하면 다른 고루틴은 unlock이 호출될 때까지 기다려야 합니다.

예를 들어 여러 고루틴이 공유 자원에 접근할 때 뮤텍스를 사용할 수 있습니다.


package main

import (
    "fmt"
    "sync"
)

var count int
var mutex sync.Mutex

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go incCounter(&wg)
    }
    wg.Wait()
    fmt.Println(count)
}

func incCounter(wg *sync.WaitGroup) {
    mutex.Lock()
    count++
    mutex.Unlock()
    wg.Done()
} 

위 예제에서 여러 고루틴이 incCounter 함수를 동시에 호출하여 공유 자원인 count 변수를 증가시킵니다.

이때 뮤텍스 lock과 unlock을 사용하여 한번에 하나의 고루틴만 이 크리티컬 섹션에 들어갈 수 있도록 보호합니다. 결과적으로 count 변수가 정상적으로 10까지 증가합니다.

만약 뮤텍스를 사용하지 않는다면 여러 고루틴이 동시에 count를 읽고 쓰므로 race condition이 발생하여 오작동할 수 있습니다.

뮤텍스는 고루틴간에 안전하게 데이터를 공유하고 race condition을 방지하기 위한 매우 유용한 기법입니다.

이상 뮤텍스(Mutex)의 개념과 활용 예에 대해 설명드렸습니다. 도움이 되었기를 바랍니다.

프로그램언어 고(Go)에서의 웨이트그룹 WaitGroup 활용

고(Go)언어의 WaitGroup은 고루틴의 동기화를 위해 사용되는 유용한 기능입니다.

WaitGroup을 사용하면 한 고루틴이 다른 고루틴들의 작업이 끝날 때까지 기다리게 할 수 있습니다. 주로 병렬 처리任务이나 동기화가 필요한 경우에 사용합니다.

WaitGroup을 사용하기 위해서는 먼저 WaitGroup 인스턴스를 하나 생성합니다.

var wg sync.WaitGroup

그리고 각 고루틴을 시작하기 전에 Add() 메서드로 WaitGroup의 카운터를 1씩 올립니다.

wg.Add(1)

이러면 WaitGroup의 internal counter가 1씩 증가합니다.

그리고 고루틴 내부에서 모든 일을 마치면 Done()메서드를 호출하여 카운터를 1 줄입니다.

wg.Done()

마지막으로 기다리고 싶은 곳에서 Wait() 메서드를 사용하여 internal counter가 0이 될 때까지 기다립니다.

wg.Wait()

여기서 모든 고루틴의 wg.Done() 이 호출되어 카운터가 0이 되면 다시 진행합니다.

예를들어 병렬작업 처리 시에 유용합니다.


func main() {

  var wg sync.WaitGroup
  
  wg.Add(2) // counter 2증가

  go func() {
    defer wg.Done() // Done으로 counter 1감소 
    // go routine1 작업
  }()

  go func() {
    defer wg.Done() // Done으로 counter 1감소
    // go routine2 작업 
  }()

  wg.Wait() // counter가 0될때까지 기다림  

  // 모든 고루틴 작업 완료 후 이어서 진행

}

이와 같이 WaitGroup을 사용하면 고루틴들의 작업 완료를 기다리는 동기화를 쉽게 구현할 수 있습니다. 병렬처리 작업이 많은 경우에 아주 유용하게 사용할 수 있는 기능이니 잘 활용하시기 바랍니다.

이해가 잘 되셨기를 바라며, 더 궁금한 점이 있으시면 언제든 질문 해주세요. 해당 내용에 대한 설명이 모자랐다면 죄송합니다.

프로그램언어 고(Go)에서의 채널 Channel을 이용한 동기화

package main

import "fmt"

func main() {

    c := make(chan int)

    go func() {
        fmt.Println("goroutine 시작")

        c <- 1 // channel에 값 보내기

        fmt.Println("goroutine 종료")
    }()

    <-c // channel에서 값 가져오기

    fmt.Println("main 함수 종료")
}

프로그램언어 Go에서 채널(Channel)은 고루틴(Goroutine)들 간의 통신과 동기화를 위해 사용됩니다.

예를 들어 위의 코드에서 main 함수가 종료되기 전에 별도의 고루틴에서 실행되는 작업이 끝나길 기다려야 하는 경우 채널을 활용할 수 있습니다.

c := make(chan int) 코드로 채널을 만듭니다.

그리고 고루틴 내에서 c <- 1 코드로 채널에 값을 보냅니다.

main 함수에서는 <-c 코드로 채널에서 값을 가져올 때까지 기다립니다.

즉, 채널에 값이 보내질 때까지 main 함수가 블로킹되어 기다리다가, 채널을 통해 값이 전달되면 다시 실행을 재개하는 것입니다.

이를 통해 main 함수는 고루틴 내의 작업이 끝날 때까지 기다릴 수 있어 동기화가 가능합니다.

고루틴과 채널의 이러한 조합을 통해 동시성 프로그래밍이 가능하다는 장점이 있습니다.

프로그램언어 고(Go)에서의 고루틴 동기화 문제 해결 방안

Go 언어의 고루틴은 비동기로 실행되므로 서로간의 동기화가 필요한 경우가 있습니다. 고루틴간의 동기화 문제를 해결할 수 있는 몇가지 기본적인 방법이 있습니다.


package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {

    // WaitGroup을 사용한 방법
    wg.Add(2) // 2개의 고루틴을 기다림

    go func() {
        defer wg.Done() // 고루틴 완료시 Done 메서드 호출
        fmt.Println("고루틴 1 실행")
    }()

    go func() {
        defer wg.Done() 
        fmt.Println("고루틴 2 실행")
    }()

    wg.Wait() // 모든 고루틴이 끝날 때까지 기다림
    fmt.Println("모든 고루틴 완료")

}

위 예제코드에서 보면 WaitGroup을 사용하여 고루틴들의 동기화를 맞추고 있습니다.

WaitGroup은 고루틴의 개수를 세어주는 카운터 역할을 합니다. Add 메서드로 수행해야할 고루틴의 개수를 정의합니다.

각 고루틴이 끝날 때 마다 Done 메서드로 카운터를 줄여나갑니다.

마지막으로 주 고루틴에서 Wait 메서드를 사용하여 모든 고루틴이 끝날 때까지 기다립니다.


package main

import "sync"

func main() {

    var mutex = &sync.Mutex{}

    go func() {
        mutex.Lock() // 뮤텍스 잠금
        mutex.Unlock() // 뮤텍스 잠금 해제
    }()

    mutex.Lock()
    mutex.Unlock()

}  

Mutex도 고루틴간 동기화에 자주 사용되는 방법입니다.

Mutex의 Lock 메서드로 크리티컬 섹션(중요 영역)에 접근할 수 있는 고루틴을 하나로 제한합니다.

Unlock 메서드로 해당 섹션의 사용을 끝내면, 다른 고루틴도 접근이 가능합니다.

  
package main

import "sync"

var chan1 = make(chan bool) 

func main() {

    go func() {
        <- chan1 // channel로 부터 신호를 받을 때까지 대기
    }()

    chan1 <- true // channel로 신호 전송

}

Channel을 이용하는 방법도 있습니다.

Channel을 통해 고루틴간 통신과 동기화를 처리할 수 있습니다.

위 예제는 channel로 부터 신호가 올 때까지 대기하는 방법을 보여줍니다.

이 외에도 공유 메모리, 명시적 Lock 등의 다양한 방법을 통해 고루틴간 동기화 문제를 해결할 수 있습니다.

프로그램언어 고(Go)에서의 동기화를 위한 패턴들

프로그램언어 고(Go)에서 동기화를 위한 주요 패턴들은 다음과 같습니다.

package main

import (
    "fmt"
    "sync"
)

var (
    // mu는 주요 자원에 대한 접근을 동기화하기 위한 뮤텍스입니다.
    mu sync.Mutex
    // resource는 보호되어야 하는 주요 자원입니다.
    resource int
)

위의 코드에서 보듯이, Go언어에서는 주로 sync 패키지의 Mutex와 RWMutex를 사용하여 동기화를 구현합니다.

Muetx는 상호배제(Mutual Exclusion)를 위한 락(Lock)을 제공합니다. critical section에 접근할 수 있는 고루틴은 오직 하나입니다. 다른 고루틴은 Lock()메서드를 통해 뮤텍스를 획득할 때까지 기다려야 합니다.

func updateResource{
    mu.Lock()
    defer mu.Unlock()
    
    // 이곳은 크리티컬 섹션(critical section)입니다.
    resource++ 
}

RWMutex는 읽기와 쓰기를 위한 별도의 락을 제공합니다. 여러 고루틴이 동시에 읽기 작업을 수행할 수 있습니다. 쓰기 작업 시에만 배타적 접근이 보장됩니다.

func readResource{
    mu.RLock()
    defer mu.RUnlock()

    // 리소스에 대한 읽기 작업을 수행합니다. 
}

func writeResource{
    mu.Lock()
    defer mu.Unlock()
    
    // 리소스에 대한 쓰기 작업을 수행합니다.
}

이 외에도, Go 언어만의 고유한 동기화 기법인 Channel과 select를 사용할 수도 있습니다.

Channel을 통해 고루틴간 통신이 이루어지며, 이를 통해 동기화를 구현할 수 있습니다. select를 사용하면 channel의 읽기/쓰기 준비 여부를 효과적으로 관리할 수 있습니다.

Go언어의 동기화 패턴에 대한 간략한 설명입니다. 예제코드와 함께 조금 더 자세히 설명하였습니다. Go의 동시성은 상당히 복잡한 주제이므로 이 정도 설명으로는 다소 부족할 수 있겠지만, 개략적인 이해에 도움이 되었으면 합니다.

Leave a Comment